diff --git a/webapp/channels/src/actions/notification_actions.jsx b/webapp/channels/src/actions/notification_actions.jsx index 95e3d450e2..088cf1aedb 100644 --- a/webapp/channels/src/actions/notification_actions.jsx +++ b/webapp/channels/src/actions/notification_actions.jsx @@ -14,9 +14,11 @@ import {isSystemMessage, isUserAddedInChannel} from 'mattermost-redux/utils/post import {displayUsername} from 'mattermost-redux/utils/user_utils'; import {isThreadOpen} from 'selectors/views/threads'; +import {getChannelURL, getPermalinkURL} from 'selectors/urls'; import {getHistory} from 'utils/browser_history'; import Constants, {NotificationLevels, UserStatuses} from 'utils/constants'; +import * as NotificationSounds from 'utils/notification_sounds'; import {showNotification} from 'utils/notifications'; import {isDesktopApp, isMobileApp, isWindowsApp} from 'utils/user_agent'; import * as Utils from 'utils/utils'; @@ -178,17 +180,17 @@ export function sendDesktopNotification(post, msgProps) { if (notify) { const updatedState = getState(); - let url = Utils.getChannelURL(updatedState, channel, teamId); + let url = getChannelURL(updatedState, channel, teamId); if (isCrtReply) { - url = Utils.getPermalinkURL(updatedState, teamId, post.id); + url = getPermalinkURL(updatedState, teamId, post.id); } dispatch(notifyMe(title, body, channel, teamId, !sound, soundName, url)); //Don't add extra sounds on native desktop clients if (sound && !isDesktopApp() && !isMobileApp()) { - Utils.ding(soundName); + NotificationSounds.ding(soundName); } } }; diff --git a/webapp/channels/src/actions/notification_actions.test.js b/webapp/channels/src/actions/notification_actions.test.js index 56fbca06db..c8b5a70599 100644 --- a/webapp/channels/src/actions/notification_actions.test.js +++ b/webapp/channels/src/actions/notification_actions.test.js @@ -5,8 +5,8 @@ import testConfigureStore from 'tests/test_store'; import {getHistory} from 'utils/browser_history'; import Constants, {NotificationLevels, UserStatuses} from 'utils/constants'; +import * as NotificationSounds from 'utils/notification_sounds'; import * as utils from 'utils/notifications'; -import * as baseUtils from 'utils/utils'; import {sendDesktopNotification} from './notification_actions'; @@ -22,7 +22,7 @@ describe('notification_actions', () => { beforeEach(() => { spy = jest.spyOn(utils, 'showNotification'); - baseUtils.ding = jest.fn(); + NotificationSounds.ding = jest.fn(); crt = { user_id: 'current_user_id', @@ -315,7 +315,7 @@ describe('notification_actions', () => { }); test('should default sound when no sound is specified', () => { - const dingSpy = jest.spyOn(baseUtils, 'ding'); + const dingSpy = jest.spyOn(NotificationSounds, 'ding'); baseState.entities.users.profiles.current_user_id.notify_props.desktop_sound = 'true'; const store = testConfigureStore(baseState); return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => { @@ -324,7 +324,7 @@ describe('notification_actions', () => { }); test('should use specified sound when specified', () => { - const dingSpy = jest.spyOn(baseUtils, 'ding'); + const dingSpy = jest.spyOn(NotificationSounds, 'ding'); baseState.entities.users.profiles.current_user_id.notify_props.desktop_sound = 'true'; baseState.entities.users.profiles.current_user_id.notify_props.desktop_notification_sound = 'Crackle'; const store = testConfigureStore(baseState); diff --git a/webapp/channels/src/components/advanced_create_comment/advanced_create_comment.tsx b/webapp/channels/src/components/advanced_create_comment/advanced_create_comment.tsx index b0679ea814..a484dc4bb2 100644 --- a/webapp/channels/src/components/advanced_create_comment/advanced_create_comment.tsx +++ b/webapp/channels/src/components/advanced_create_comment/advanced_create_comment.tsx @@ -13,6 +13,7 @@ import * as GlobalActions from 'actions/global_actions'; import Constants, {AdvancedTextEditor as AdvancedTextEditorConst, Locations, ModalIdentifiers, Preferences} from 'utils/constants'; import {PreferenceType} from '@mattermost/types/preferences'; +import * as Keyboard from 'utils/keyboard'; import * as UserAgent from 'utils/user_agent'; import * as Utils from 'utils/utils'; import { @@ -819,11 +820,11 @@ class AdvancedCreateComment extends React.PureComponent { handleKeyDown = (e: React.KeyboardEvent) => { const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey; - const lastMessageReactionKeyCombo = ctrlOrMetaKeyPressed && e.shiftKey && Utils.isKeyPressed(e, KeyCodes.BACK_SLASH); + const lastMessageReactionKeyCombo = ctrlOrMetaKeyPressed && e.shiftKey && Keyboard.isKeyPressed(e, KeyCodes.BACK_SLASH); - const ctrlKeyCombo = Utils.cmdOrCtrlPressed(e) && !e.altKey && !e.shiftKey; - const ctrlAltCombo = Utils.cmdOrCtrlPressed(e, true) && e.altKey; - const shiftAltCombo = !Utils.cmdOrCtrlPressed(e) && e.shiftKey && e.altKey; + const ctrlKeyCombo = Keyboard.cmdOrCtrlPressed(e) && !e.altKey && !e.shiftKey; + const ctrlAltCombo = Keyboard.cmdOrCtrlPressed(e, true) && e.altKey; + const shiftAltCombo = !Keyboard.cmdOrCtrlPressed(e) && e.shiftKey && e.altKey; // listen for line break key combo and insert new line character if (Utils.isUnhandledLineBreakKeyCombo(e)) { @@ -838,7 +839,7 @@ class AdvancedCreateComment extends React.PureComponent { if ( (this.props.ctrlSend || this.props.codeBlockOnCtrlEnter) && - Utils.isKeyPressed(e, KeyCodes.ENTER) && + Keyboard.isKeyPressed(e, KeyCodes.ENTER) && (e.ctrlKey || e.metaKey) ) { this.setShowPreview(false); @@ -849,7 +850,7 @@ class AdvancedCreateComment extends React.PureComponent { const draft = this.state.draft!; const {message} = draft; - if (Utils.isKeyPressed(e, KeyCodes.ESCAPE)) { + if (Keyboard.isKeyPressed(e, KeyCodes.ESCAPE)) { this.textboxRef.current?.blur(); } @@ -858,7 +859,7 @@ class AdvancedCreateComment extends React.PureComponent { !e.metaKey && !e.altKey && !e.shiftKey && - Utils.isKeyPressed(e, KeyCodes.UP) && + Keyboard.isKeyPressed(e, KeyCodes.UP) && message === '' ) { e.preventDefault(); @@ -879,13 +880,13 @@ class AdvancedCreateComment extends React.PureComponent { } = e.target as TextboxElement; if (ctrlKeyCombo) { - if (Utils.isKeyPressed(e, KeyCodes.UP)) { + if (Keyboard.isKeyPressed(e, KeyCodes.UP)) { e.preventDefault(); this.props.onMoveHistoryIndexBack(); - } else if (Utils.isKeyPressed(e, KeyCodes.DOWN)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.DOWN)) { e.preventDefault(); this.props.onMoveHistoryIndexForward(); - } else if (Utils.isKeyPressed(e, KeyCodes.B)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.B)) { e.stopPropagation(); e.preventDefault(); this.applyMarkdown({ @@ -894,7 +895,7 @@ class AdvancedCreateComment extends React.PureComponent { selectionEnd, message: value, }); - } else if (Utils.isKeyPressed(e, KeyCodes.I)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.I)) { e.stopPropagation(); e.preventDefault(); this.applyMarkdown({ @@ -905,7 +906,7 @@ class AdvancedCreateComment extends React.PureComponent { }); } } else if (ctrlAltCombo) { - if (Utils.isKeyPressed(e, KeyCodes.K)) { + if (Keyboard.isKeyPressed(e, KeyCodes.K)) { e.stopPropagation(); e.preventDefault(); this.applyMarkdown({ @@ -914,7 +915,7 @@ class AdvancedCreateComment extends React.PureComponent { selectionEnd, message: value, }); - } else if (Utils.isKeyPressed(e, KeyCodes.C)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.C)) { e.stopPropagation(); e.preventDefault(); this.applyMarkdown({ @@ -923,21 +924,21 @@ class AdvancedCreateComment extends React.PureComponent { selectionEnd, message: value, }); - } else if (Utils.isKeyPressed(e, KeyCodes.E)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.E)) { e.stopPropagation(); e.preventDefault(); this.toggleEmojiPicker(); - } else if (Utils.isKeyPressed(e, KeyCodes.T)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.T)) { e.stopPropagation(); e.preventDefault(); this.toggleAdvanceTextEditor(); - } else if (Utils.isKeyPressed(e, KeyCodes.P) && draft.message.length) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.P) && draft.message.length) { e.stopPropagation(); e.preventDefault(); this.setShowPreview(!this.props.shouldShowPreview); } } else if (shiftAltCombo) { - if (Utils.isKeyPressed(e, KeyCodes.X)) { + if (Keyboard.isKeyPressed(e, KeyCodes.X)) { e.stopPropagation(); e.preventDefault(); this.applyMarkdown({ @@ -946,7 +947,7 @@ class AdvancedCreateComment extends React.PureComponent { selectionEnd, message: value, }); - } else if (Utils.isKeyPressed(e, KeyCodes.SEVEN)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.SEVEN)) { e.preventDefault(); this.applyMarkdown({ markdownMode: 'ol', @@ -954,7 +955,7 @@ class AdvancedCreateComment extends React.PureComponent { selectionEnd, message: value, }); - } else if (Utils.isKeyPressed(e, KeyCodes.EIGHT)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.EIGHT)) { e.preventDefault(); this.applyMarkdown({ markdownMode: 'ul', @@ -962,7 +963,7 @@ class AdvancedCreateComment extends React.PureComponent { selectionEnd, message: value, }); - } else if (Utils.isKeyPressed(e, KeyCodes.NINE)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.NINE)) { e.preventDefault(); this.applyMarkdown({ markdownMode: 'quote', diff --git a/webapp/channels/src/components/advanced_create_post/advanced_create_post.tsx b/webapp/channels/src/components/advanced_create_post/advanced_create_post.tsx index 630ad71188..a8bcd5bbba 100644 --- a/webapp/channels/src/components/advanced_create_post/advanced_create_post.tsx +++ b/webapp/channels/src/components/advanced_create_post/advanced_create_post.tsx @@ -22,6 +22,7 @@ import Constants, { Preferences, AdvancedTextEditor as AdvancedTextEditorConst, } from 'utils/constants'; +import * as Keyboard from 'utils/keyboard'; import { containsAtChannel, specialMentionsInText, @@ -34,7 +35,6 @@ import { } from 'utils/post_utils'; import {getTable, hasHtmlLink, formatMarkdownMessage, formatGithubCodePaste, isGitHubCodeBlock} from 'utils/paste'; import * as UserAgent from 'utils/user_agent'; -import {isMac} from 'utils/utils'; import * as Utils from 'utils/utils'; import EmojiMap from 'utils/emoji_map'; import {applyMarkdown, ApplyMarkdownOptions} from 'utils/markdown/apply_markdown'; @@ -1096,7 +1096,7 @@ class AdvancedCreatePost extends React.PureComponent { documentKeyHandler = (e: KeyboardEvent) => { const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey; - const lastMessageReactionKeyCombo = ctrlOrMetaKeyPressed && e.shiftKey && Utils.isKeyPressed(e, KeyCodes.BACK_SLASH); + const lastMessageReactionKeyCombo = ctrlOrMetaKeyPressed && e.shiftKey && Keyboard.isKeyPressed(e, KeyCodes.BACK_SLASH); if (lastMessageReactionKeyCombo) { this.reactToLastMessage(e); return; @@ -1134,12 +1134,12 @@ class AdvancedCreatePost extends React.PureComponent { const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey; const ctrlEnterKeyCombo = (this.props.ctrlSend || this.props.codeBlockOnCtrlEnter) && - Utils.isKeyPressed(e, KeyCodes.ENTER) && + Keyboard.isKeyPressed(e, KeyCodes.ENTER) && ctrlOrMetaKeyPressed; - const ctrlKeyCombo = Utils.cmdOrCtrlPressed(e) && !e.altKey && !e.shiftKey; - const ctrlAltCombo = Utils.cmdOrCtrlPressed(e, true) && e.altKey; - const shiftAltCombo = !Utils.cmdOrCtrlPressed(e) && e.shiftKey && e.altKey; + const ctrlKeyCombo = Keyboard.cmdOrCtrlPressed(e) && !e.altKey && !e.shiftKey; + const ctrlAltCombo = Keyboard.cmdOrCtrlPressed(e, true) && e.altKey; + const shiftAltCombo = !Keyboard.cmdOrCtrlPressed(e) && e.shiftKey && e.altKey; // listen for line break key combo and insert new line character if (Utils.isUnhandledLineBreakKeyCombo(e)) { @@ -1155,7 +1155,7 @@ class AdvancedCreatePost extends React.PureComponent { const {message} = this.state; - if (Utils.isKeyPressed(e, KeyCodes.ESCAPE)) { + if (Keyboard.isKeyPressed(e, KeyCodes.ESCAPE)) { this.textboxRef.current?.blur(); } @@ -1164,7 +1164,7 @@ class AdvancedCreatePost extends React.PureComponent { !e.metaKey && !e.altKey && !e.shiftKey && - Utils.isKeyPressed(e, KeyCodes.UP) && + Keyboard.isKeyPressed(e, KeyCodes.UP) && message === '' ) { e.preventDefault(); @@ -1182,15 +1182,15 @@ class AdvancedCreatePost extends React.PureComponent { } = e.target as TextboxElement; if (ctrlKeyCombo) { - if (draftMessageIsEmpty && Utils.isKeyPressed(e, KeyCodes.UP)) { + if (draftMessageIsEmpty && Keyboard.isKeyPressed(e, KeyCodes.UP)) { e.stopPropagation(); e.preventDefault(); this.loadPrevMessage(e); - } else if (draftMessageIsEmpty && Utils.isKeyPressed(e, KeyCodes.DOWN)) { + } else if (draftMessageIsEmpty && Keyboard.isKeyPressed(e, KeyCodes.DOWN)) { e.stopPropagation(); e.preventDefault(); this.loadNextMessage(e); - } else if (Utils.isKeyPressed(e, KeyCodes.B)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.B)) { e.stopPropagation(); e.preventDefault(); this.applyMarkdown({ @@ -1199,7 +1199,7 @@ class AdvancedCreatePost extends React.PureComponent { selectionEnd, message: value, }); - } else if (Utils.isKeyPressed(e, KeyCodes.I)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.I)) { e.stopPropagation(); e.preventDefault(); this.applyMarkdown({ @@ -1210,7 +1210,7 @@ class AdvancedCreatePost extends React.PureComponent { }); } } else if (ctrlAltCombo) { - if (Utils.isKeyPressed(e, KeyCodes.K)) { + if (Keyboard.isKeyPressed(e, KeyCodes.K)) { e.stopPropagation(); e.preventDefault(); this.applyMarkdown({ @@ -1219,7 +1219,7 @@ class AdvancedCreatePost extends React.PureComponent { selectionEnd, message: value, }); - } else if (Utils.isKeyPressed(e, KeyCodes.C)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.C)) { e.stopPropagation(); e.preventDefault(); this.applyMarkdown({ @@ -1228,21 +1228,21 @@ class AdvancedCreatePost extends React.PureComponent { selectionEnd, message: value, }); - } else if (Utils.isKeyPressed(e, KeyCodes.E)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.E)) { e.stopPropagation(); e.preventDefault(); this.toggleEmojiPicker(); - } else if (Utils.isKeyPressed(e, KeyCodes.T)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.T)) { e.stopPropagation(); e.preventDefault(); this.toggleAdvanceTextEditor(); - } else if (Utils.isKeyPressed(e, KeyCodes.P) && message.length) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.P) && message.length) { e.stopPropagation(); e.preventDefault(); this.setShowPreview(!this.props.shouldShowPreview); } } else if (shiftAltCombo) { - if (Utils.isKeyPressed(e, KeyCodes.X)) { + if (Keyboard.isKeyPressed(e, KeyCodes.X)) { e.stopPropagation(); e.preventDefault(); this.applyMarkdown({ @@ -1251,7 +1251,7 @@ class AdvancedCreatePost extends React.PureComponent { selectionEnd, message: value, }); - } else if (Utils.isKeyPressed(e, KeyCodes.SEVEN)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.SEVEN)) { e.preventDefault(); this.applyMarkdown({ markdownMode: 'ol', @@ -1259,7 +1259,7 @@ class AdvancedCreatePost extends React.PureComponent { selectionEnd, message: value, }); - } else if (Utils.isKeyPressed(e, KeyCodes.EIGHT)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.EIGHT)) { e.preventDefault(); this.applyMarkdown({ markdownMode: 'ul', @@ -1267,7 +1267,7 @@ class AdvancedCreatePost extends React.PureComponent { selectionEnd, message: value, }); - } else if (Utils.isKeyPressed(e, KeyCodes.NINE)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.NINE)) { e.preventDefault(); this.applyMarkdown({ markdownMode: 'quote', @@ -1277,21 +1277,21 @@ class AdvancedCreatePost extends React.PureComponent { }); } } - const upKeyOnly = !ctrlOrMetaKeyPressed && !e.altKey && !e.shiftKey && Utils.isKeyPressed(e, KeyCodes.UP); - const shiftUpKeyCombo = !ctrlOrMetaKeyPressed && !e.altKey && e.shiftKey && Utils.isKeyPressed(e, KeyCodes.UP); - const ctrlShiftCombo = Utils.cmdOrCtrlPressed(e, true) && e.shiftKey; + const upKeyOnly = !ctrlOrMetaKeyPressed && !e.altKey && !e.shiftKey && Keyboard.isKeyPressed(e, KeyCodes.UP); + const shiftUpKeyCombo = !ctrlOrMetaKeyPressed && !e.altKey && e.shiftKey && Keyboard.isKeyPressed(e, KeyCodes.UP); + const ctrlShiftCombo = Keyboard.cmdOrCtrlPressed(e, true) && e.shiftKey; if (upKeyOnly && messageIsEmpty) { this.editLastPost(e); } else if (shiftUpKeyCombo && messageIsEmpty) { this.replyToLastPost(e); - } else if (ctrlShiftCombo && Utils.isKeyPressed(e, KeyCodes.E)) { + } else if (ctrlShiftCombo && Keyboard.isKeyPressed(e, KeyCodes.E)) { e.stopPropagation(); e.preventDefault(); this.toggleEmojiPicker(); - } else if (((isMac() && ctrlShiftCombo) || (!isMac() && ctrlAltCombo)) && Utils.isKeyPressed(e, KeyCodes.P) && this.state.message.length) { + } else if (((UserAgent.isMac() && ctrlShiftCombo) || (!UserAgent.isMac() && ctrlAltCombo)) && Keyboard.isKeyPressed(e, KeyCodes.P) && this.state.message.length) { this.setShowPreview(!this.props.shouldShowPreview); - } else if (ctrlAltCombo && Utils.isKeyPressed(e, KeyCodes.T)) { + } else if (ctrlAltCombo && Keyboard.isKeyPressed(e, KeyCodes.T)) { this.toggleAdvanceTextEditor(); } }; diff --git a/webapp/channels/src/components/at_mention/at_mention.tsx b/webapp/channels/src/components/at_mention/at_mention.tsx index 4bb914369f..e8e51ca423 100644 --- a/webapp/channels/src/components/at_mention/at_mention.tsx +++ b/webapp/channels/src/components/at_mention/at_mention.tsx @@ -12,9 +12,10 @@ import {Group} from '@mattermost/types/groups'; import ProfilePopover from 'components/profile_popover'; import {popOverOverlayPosition} from 'utils/position_utils'; +import {isKeyPressed} from 'utils/keyboard'; import {getUserOrGroupFromMentionName} from 'utils/post_utils'; import Constants from 'utils/constants'; -import {getViewportSize, isKeyPressed} from 'utils/utils'; +import {getViewportSize} from 'utils/utils'; import AtMentionGroup from 'components/at_mention/at_mention_group'; diff --git a/webapp/channels/src/components/at_mention/at_mention_group.tsx b/webapp/channels/src/components/at_mention/at_mention_group.tsx index aaa449eef9..fa1fd3abd5 100644 --- a/webapp/channels/src/components/at_mention/at_mention_group.tsx +++ b/webapp/channels/src/components/at_mention/at_mention_group.tsx @@ -12,8 +12,9 @@ import ProfilePopover from 'components/profile_popover'; import UserGroupPopover from 'components/user_group_popover'; import Constants, {A11yCustomEventTypes, A11yFocusEventDetail} from 'utils/constants'; +import {isKeyPressed} from 'utils/keyboard'; import {popOverOverlayPosition} from 'utils/position_utils'; -import {getViewportSize, isKeyPressed} from 'utils/utils'; +import {getViewportSize} from 'utils/utils'; import {MAX_LIST_HEIGHT, getListHeight, VIEWPORT_SCALE_FACTOR} from 'components/user_group_popover/group_member_list/group_member_list'; diff --git a/webapp/channels/src/components/channel_members_rhs/action_bar.tsx b/webapp/channels/src/components/channel_members_rhs/action_bar.tsx index 90d08d9064..44e4d80796 100644 --- a/webapp/channels/src/components/channel_members_rhs/action_bar.tsx +++ b/webapp/channels/src/components/channel_members_rhs/action_bar.tsx @@ -6,7 +6,7 @@ import {FormattedMessage} from 'react-intl'; import styled from 'styled-components'; import Constants from 'utils/constants'; -import {isKeyPressed} from 'utils/utils'; +import {isKeyPressed} from 'utils/keyboard'; const Title = styled.div` flex:1; diff --git a/webapp/channels/src/components/custom_status/custom_status_modal.tsx b/webapp/channels/src/components/custom_status/custom_status_modal.tsx index 28f2efd9aa..6f18e0415f 100644 --- a/webapp/channels/src/components/custom_status/custom_status_modal.tsx +++ b/webapp/channels/src/components/custom_status/custom_status_modal.tsx @@ -25,7 +25,8 @@ import {GlobalState} from 'types/store'; import {getCurrentMomentForTimezone} from 'utils/timezone'; import {A11yCustomEventTypes, A11yFocusEventDetail, Constants, ModalIdentifiers} from 'utils/constants'; import {t} from 'utils/i18n'; -import {isKeyPressed, localizeMessage} from 'utils/utils'; +import {isKeyPressed} from 'utils/keyboard'; +import {localizeMessage} from 'utils/utils'; import CustomStatusSuggestion from 'components/custom_status/custom_status_suggestion'; import ExpiryMenu from 'components/custom_status/expiry_menu'; diff --git a/webapp/channels/src/components/custom_status/date_time_input.tsx b/webapp/channels/src/components/custom_status/date_time_input.tsx index 8dde00eded..8c1f83b3a8 100644 --- a/webapp/channels/src/components/custom_status/date_time_input.tsx +++ b/webapp/channels/src/components/custom_status/date_time_input.tsx @@ -17,7 +17,8 @@ import DatePicker from 'components/date_picker'; import Menu from 'components/widgets/menu/menu'; import Timestamp from 'components/timestamp'; import {getCurrentLocale} from 'selectors/i18n'; -import {isKeyPressed, localizeMessage} from 'utils/utils'; +import {isKeyPressed} from 'utils/keyboard'; +import {localizeMessage} from 'utils/utils'; import {getCurrentMomentForTimezone} from 'utils/timezone'; import Constants, {A11yCustomEventTypes, A11yFocusEventDetail} from 'utils/constants'; diff --git a/webapp/channels/src/components/dnd_custom_time_picker_modal/dnd_custom_time_picker_modal.tsx b/webapp/channels/src/components/dnd_custom_time_picker_modal/dnd_custom_time_picker_modal.tsx index 8e83fb47cd..f4932ceb86 100644 --- a/webapp/channels/src/components/dnd_custom_time_picker_modal/dnd_custom_time_picker_modal.tsx +++ b/webapp/channels/src/components/dnd_custom_time_picker_modal/dnd_custom_time_picker_modal.tsx @@ -22,7 +22,8 @@ import MenuWrapper from 'components/widgets/menu/menu_wrapper'; import './dnd_custom_time_picker_modal.scss'; import {toUTCUnix} from 'utils/datetime'; -import {isKeyPressed, localizeMessage} from 'utils/utils'; +import {isKeyPressed} from 'utils/keyboard'; +import {localizeMessage} from 'utils/utils'; import Input from 'components/widgets/inputs/input/input'; import DatePicker from 'components/date_picker'; diff --git a/webapp/channels/src/components/dot_menu/dot_menu.tsx b/webapp/channels/src/components/dot_menu/dot_menu.tsx index 46067b6abe..89068ae450 100644 --- a/webapp/channels/src/components/dot_menu/dot_menu.tsx +++ b/webapp/channels/src/components/dot_menu/dot_menu.tsx @@ -28,6 +28,7 @@ import Permissions from 'mattermost-redux/constants/permissions'; import {Locations, ModalIdentifiers, Constants, TELEMETRY_LABELS} from 'utils/constants'; import DeletePostModal from 'components/delete_post_modal'; import DelayedAction from 'utils/delayed_action'; +import * as Keyboard from 'utils/keyboard'; import * as PostUtils from 'utils/post_utils'; import * as Menu from 'components/menu'; import * as Utils from 'utils/utils'; @@ -337,61 +338,61 @@ export class DotMenuClass extends React.PureComponent { const isShiftKeyPressed = e.shiftKey; switch (true) { - case Utils.isKeyPressed(e, Constants.KeyCodes.R): + case Keyboard.isKeyPressed(e, Constants.KeyCodes.R): this.handleCommentClick(e); this.handleDropdownOpened(false); break; // edit post - case Utils.isKeyPressed(e, Constants.KeyCodes.E): + case Keyboard.isKeyPressed(e, Constants.KeyCodes.E): this.handleEditMenuItemActivated(e); this.handleDropdownOpened(false); break; // follow thread - case Utils.isKeyPressed(e, Constants.KeyCodes.F) && !isShiftKeyPressed: + case Keyboard.isKeyPressed(e, Constants.KeyCodes.F) && !isShiftKeyPressed: this.handleSetThreadFollow(e); this.handleDropdownOpened(false); break; // forward post - case Utils.isKeyPressed(e, Constants.KeyCodes.F) && isShiftKeyPressed: + case Keyboard.isKeyPressed(e, Constants.KeyCodes.F) && isShiftKeyPressed: this.handleForwardMenuItemActivated(e); this.handleDropdownOpened(false); break; // copy link - case Utils.isKeyPressed(e, Constants.KeyCodes.K): + case Keyboard.isKeyPressed(e, Constants.KeyCodes.K): this.copyLink(e); this.handleDropdownOpened(false); break; // copy text - case Utils.isKeyPressed(e, Constants.KeyCodes.C): + case Keyboard.isKeyPressed(e, Constants.KeyCodes.C): this.copyText(e); this.handleDropdownOpened(false); break; // delete post - case Utils.isKeyPressed(e, Constants.KeyCodes.DELETE): + case Keyboard.isKeyPressed(e, Constants.KeyCodes.DELETE): this.handleDeleteMenuItemActivated(e); this.handleDropdownOpened(false); break; // pin / unpin - case Utils.isKeyPressed(e, Constants.KeyCodes.P): + case Keyboard.isKeyPressed(e, Constants.KeyCodes.P): this.handlePinMenuItemActivated(e); this.handleDropdownOpened(false); break; // save / unsave - case Utils.isKeyPressed(e, Constants.KeyCodes.S): + case Keyboard.isKeyPressed(e, Constants.KeyCodes.S): this.handleFlagMenuItemActivated(e); this.handleDropdownOpened(false); break; // mark as unread - case Utils.isKeyPressed(e, Constants.KeyCodes.U): + case Keyboard.isKeyPressed(e, Constants.KeyCodes.U): this.handleMarkPostAsUnread(e); this.handleDropdownOpened(false); break; diff --git a/webapp/channels/src/components/drafts/channel_draft/index.ts b/webapp/channels/src/components/drafts/channel_draft/index.ts index e34d66c351..b3722f1cdf 100644 --- a/webapp/channels/src/components/drafts/channel_draft/index.ts +++ b/webapp/channels/src/components/drafts/channel_draft/index.ts @@ -6,7 +6,7 @@ import {connect} from 'react-redux'; import {makeGetChannel} from 'mattermost-redux/selectors/entities/channels'; import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; -import {getChannelURL} from 'utils/utils'; +import {getChannelURL} from 'selectors/urls'; import {GlobalState} from 'types/store'; diff --git a/webapp/channels/src/components/edit_channel_header_modal/edit_channel_header_modal.tsx b/webapp/channels/src/components/edit_channel_header_modal/edit_channel_header_modal.tsx index c61e3a48db..655084b44c 100644 --- a/webapp/channels/src/components/edit_channel_header_modal/edit_channel_header_modal.tsx +++ b/webapp/channels/src/components/edit_channel_header_modal/edit_channel_header_modal.tsx @@ -13,8 +13,9 @@ import Textbox, {TextboxElement} from 'components/textbox'; import TextboxClass from 'components/textbox/textbox'; import TextboxLinks from 'components/textbox/textbox_links'; import Constants from 'utils/constants'; +import {isKeyPressed} from 'utils/keyboard'; import {isMobile} from 'utils/user_agent'; -import {insertLineBreakFromKeyEvent, isKeyPressed, isUnhandledLineBreakKeyCombo, localizeMessage} from 'utils/utils'; +import {insertLineBreakFromKeyEvent, isUnhandledLineBreakKeyCombo, localizeMessage} from 'utils/utils'; const KeyCodes = Constants.KeyCodes; diff --git a/webapp/channels/src/components/edit_channel_purpose_modal/edit_channel_purpose_modal.tsx b/webapp/channels/src/components/edit_channel_purpose_modal/edit_channel_purpose_modal.tsx index 0f932581e0..e7fbc1abd6 100644 --- a/webapp/channels/src/components/edit_channel_purpose_modal/edit_channel_purpose_modal.tsx +++ b/webapp/channels/src/components/edit_channel_purpose_modal/edit_channel_purpose_modal.tsx @@ -9,6 +9,7 @@ import {Channel} from '@mattermost/types/channels'; import {ActionResult} from 'mattermost-redux/types/actions'; import Constants from 'utils/constants'; +import * as Keyboard from 'utils/keyboard'; import * as Utils from 'utils/utils'; type Actions = { @@ -68,10 +69,10 @@ export class EditChannelPurposeModal extends React.PureComponent { if (Utils.isUnhandledLineBreakKeyCombo(e)) { e.preventDefault(); this.setState({purpose: Utils.insertLineBreakFromKeyEvent(e as React.KeyboardEvent)}); - } else if (ctrlSend && Utils.isKeyPressed(e, Constants.KeyCodes.ENTER) && e.ctrlKey) { + } else if (ctrlSend && Keyboard.isKeyPressed(e, Constants.KeyCodes.ENTER) && e.ctrlKey) { e.preventDefault(); this.handleSave(); - } else if (!ctrlSend && Utils.isKeyPressed(e, Constants.KeyCodes.ENTER) && !e.shiftKey && !e.altKey) { + } else if (!ctrlSend && Keyboard.isKeyPressed(e, Constants.KeyCodes.ENTER) && !e.shiftKey && !e.altKey) { e.preventDefault(); this.handleSave(); } diff --git a/webapp/channels/src/components/edit_post/edit_post.tsx b/webapp/channels/src/components/edit_post/edit_post.tsx index 3711dca85f..81c9ea4667 100644 --- a/webapp/channels/src/components/edit_post/edit_post.tsx +++ b/webapp/channels/src/components/edit_post/edit_post.tsx @@ -10,6 +10,7 @@ import {Post} from '@mattermost/types/posts'; import {Emoji, SystemEmoji} from '@mattermost/types/emojis'; import {AppEvents, Constants, ModalIdentifiers, StoragePrefixes} from 'utils/constants'; +import * as Keyboard from 'utils/keyboard'; import { formatGithubCodePaste, formatMarkdownMessage, @@ -308,13 +309,13 @@ const EditPost = ({editingPost, actions, canEditPost, config, channelId, draft, const {ctrlSend, codeBlockOnCtrlEnter} = rest; const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey; - const ctrlKeyCombo = Utils.cmdOrCtrlPressed(e) && !e.altKey && !e.shiftKey; - const ctrlAltCombo = Utils.cmdOrCtrlPressed(e, true) && e.altKey; + const ctrlKeyCombo = Keyboard.cmdOrCtrlPressed(e) && !e.altKey && !e.shiftKey; + const ctrlAltCombo = Keyboard.cmdOrCtrlPressed(e, true) && e.altKey; const ctrlEnterKeyCombo = (ctrlSend || codeBlockOnCtrlEnter) && - Utils.isKeyPressed(e, KeyCodes.ENTER) && + Keyboard.isKeyPressed(e, KeyCodes.ENTER) && ctrlOrMetaKeyPressed; - const markdownLinkKey = Utils.isKeyPressed(e, KeyCodes.K); + const markdownLinkKey = Keyboard.isKeyPressed(e, KeyCodes.K); // listen for line break key combo and insert new line character if (Utils.isUnhandledLineBreakKeyCombo(e)) { @@ -322,7 +323,7 @@ const EditPost = ({editingPost, actions, canEditPost, config, channelId, draft, setEditText(Utils.insertLineBreakFromKeyEvent(e as React.KeyboardEvent)); } else if (ctrlEnterKeyCombo) { handleEdit(); - } else if (Utils.isKeyPressed(e, KeyCodes.ESCAPE) && !showEmojiPicker) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.ESCAPE) && !showEmojiPicker) { handleAutomatedRefocusAndExit(); } else if (ctrlAltCombo && markdownLinkKey) { applyHotkeyMarkdown({ @@ -331,14 +332,14 @@ const EditPost = ({editingPost, actions, canEditPost, config, channelId, draft, selectionEnd: e.currentTarget.selectionEnd, message: e.currentTarget.value, }); - } else if (ctrlKeyCombo && Utils.isKeyPressed(e, KeyCodes.B)) { + } else if (ctrlKeyCombo && Keyboard.isKeyPressed(e, KeyCodes.B)) { applyHotkeyMarkdown({ markdownMode: 'bold', selectionStart: e.currentTarget.selectionStart, selectionEnd: e.currentTarget.selectionEnd, message: e.currentTarget.value, }); - } else if (ctrlKeyCombo && Utils.isKeyPressed(e, KeyCodes.I)) { + } else if (ctrlKeyCombo && Keyboard.isKeyPressed(e, KeyCodes.I)) { applyHotkeyMarkdown({ markdownMode: 'italic', selectionStart: e.currentTarget.selectionStart, diff --git a/webapp/channels/src/components/edit_post/edit_post_footer.tsx b/webapp/channels/src/components/edit_post/edit_post_footer.tsx index 12971eb2cc..432c27eebe 100644 --- a/webapp/channels/src/components/edit_post/edit_post_footer.tsx +++ b/webapp/channels/src/components/edit_post/edit_post_footer.tsx @@ -8,7 +8,7 @@ import {FormattedMessage} from 'react-intl'; import {getBool} from 'mattermost-redux/selectors/entities/preferences'; import {Preferences} from 'mattermost-redux/constants'; -import {isMac} from 'utils/utils'; +import {isMac} from 'utils/user_agent'; import {GlobalState} from 'types/store'; type Props = { diff --git a/webapp/channels/src/components/file_preview_modal/file_preview_modal.tsx b/webapp/channels/src/components/file_preview_modal/file_preview_modal.tsx index eecca2d3fa..ec031aaef4 100644 --- a/webapp/channels/src/components/file_preview_modal/file_preview_modal.tsx +++ b/webapp/channels/src/components/file_preview_modal/file_preview_modal.tsx @@ -12,6 +12,7 @@ import {Post} from '@mattermost/types/posts'; import {getFileDownloadUrl, getFilePreviewUrl, getFileUrl} from 'mattermost-redux/utils/file_utils'; import LoadingImagePreview from 'components/loading_image_preview'; import Constants, {FileTypes, ZoomSettings} from 'utils/constants'; +import * as Keyboard from 'utils/keyboard'; import * as Utils from 'utils/utils'; import AudioVideoPreview from 'components/audio_video_preview'; import CodePreview from 'components/code_preview'; @@ -115,9 +116,9 @@ export default class FilePreviewModal extends React.PureComponent }; handleKeyPress = (e: KeyboardEvent) => { - if (Utils.isKeyPressed(e, KeyCodes.RIGHT)) { + if (Keyboard.isKeyPressed(e, KeyCodes.RIGHT)) { this.handleNext(); - } else if (Utils.isKeyPressed(e, KeyCodes.LEFT)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.LEFT)) { this.handlePrev(); } }; diff --git a/webapp/channels/src/components/file_upload/file_upload.tsx b/webapp/channels/src/components/file_upload/file_upload.tsx index 50ccafc021..26513bb951 100644 --- a/webapp/channels/src/components/file_upload/file_upload.tsx +++ b/webapp/channels/src/components/file_upload/file_upload.tsx @@ -12,6 +12,7 @@ import dragster from 'utils/dragster'; import Constants from 'utils/constants'; import DelayedAction from 'utils/delayed_action'; import {t} from 'utils/i18n'; +import {cmdOrCtrlPressed, isKeyPressed} from 'utils/keyboard'; import { isIosChrome, isMobileApp, @@ -19,8 +20,6 @@ import { import {getTable} from 'utils/paste'; import { clearFileInput, - cmdOrCtrlPressed, - isKeyPressed, generateId, isFileTransfer, isUriDrop, diff --git a/webapp/channels/src/components/forward_post_modal/forward_post_comment_input.tsx b/webapp/channels/src/components/forward_post_modal/forward_post_comment_input.tsx index 4923cd7ac3..bca35dd1e6 100644 --- a/webapp/channels/src/components/forward_post_modal/forward_post_comment_input.tsx +++ b/webapp/channels/src/components/forward_post_modal/forward_post_comment_input.tsx @@ -12,6 +12,7 @@ import Textbox, {TextboxClass, TextboxElement} from 'components/textbox'; import Constants from 'utils/constants'; import {applyMarkdown, ApplyMarkdownOptions} from 'utils/markdown/apply_markdown'; +import * as Keyboard from 'utils/keyboard'; import * as Utils from 'utils/utils'; import {GlobalState} from 'types/store'; @@ -77,13 +78,13 @@ const ForwardPostCommentInput = ({channelId, canForwardPost, comment, permaLinkL }; const handleKeyDown = (e: React.KeyboardEvent) => { - const ctrlKeyCombo = Utils.cmdOrCtrlPressed(e) && !e.altKey && !e.shiftKey; - const ctrlAltCombo = Utils.cmdOrCtrlPressed(e, true) && e.altKey; - const ctrlShiftCombo = Utils.cmdOrCtrlPressed(e, true) && e.shiftKey; - const markdownLinkKey = Utils.isKeyPressed(e, KeyCodes.K); + const ctrlKeyCombo = Keyboard.cmdOrCtrlPressed(e) && !e.altKey && !e.shiftKey; + const ctrlAltCombo = Keyboard.cmdOrCtrlPressed(e, true) && e.altKey; + const ctrlShiftCombo = Keyboard.cmdOrCtrlPressed(e, true) && e.shiftKey; + const markdownLinkKey = Keyboard.isKeyPressed(e, KeyCodes.K); const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey; const ctrlEnterKeyCombo = - Utils.isKeyPressed(e, KeyCodes.ENTER) && ctrlOrMetaKeyPressed; + Keyboard.isKeyPressed(e, KeyCodes.ENTER) && ctrlOrMetaKeyPressed; const {selectionStart, selectionEnd, value} = e.target as TextboxElement; @@ -98,28 +99,28 @@ const ForwardPostCommentInput = ({channelId, canForwardPost, comment, permaLinkL selectionEnd, message: value, }); - } else if (ctrlKeyCombo && Utils.isKeyPressed(e, KeyCodes.B)) { + } else if (ctrlKeyCombo && Keyboard.isKeyPressed(e, KeyCodes.B)) { applyMarkdownMode({ markdownMode: 'bold', selectionStart, selectionEnd, message: value, }); - } else if (ctrlKeyCombo && Utils.isKeyPressed(e, KeyCodes.I)) { + } else if (ctrlKeyCombo && Keyboard.isKeyPressed(e, KeyCodes.I)) { applyMarkdownMode({ markdownMode: 'italic', selectionStart, selectionEnd, message: value, }); - } else if (ctrlShiftCombo && Utils.isKeyPressed(e, KeyCodes.X)) { + } else if (ctrlShiftCombo && Keyboard.isKeyPressed(e, KeyCodes.X)) { applyMarkdownMode({ markdownMode: 'strike', selectionStart, selectionEnd, message: value, }); - } else if (ctrlShiftCombo && Utils.isKeyPressed(e, KeyCodes.E)) { + } else if (ctrlShiftCombo && Keyboard.isKeyPressed(e, KeyCodes.E)) { e.stopPropagation(); e.preventDefault(); } else if (ctrlEnterKeyCombo && canForwardPost) { diff --git a/webapp/channels/src/components/forward_post_modal/forward_post_modal.tsx b/webapp/channels/src/components/forward_post_modal/forward_post_modal.tsx index 2fbf206401..8759fddac2 100644 --- a/webapp/channels/src/components/forward_post_modal/forward_post_modal.tsx +++ b/webapp/channels/src/components/forward_post_modal/forward_post_modal.tsx @@ -16,6 +16,8 @@ import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams'; import NotificationBox from 'components/notification_box'; +import {getPermalinkURL} from 'selectors/urls'; + import {GlobalState} from 'types/store'; import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles'; @@ -28,7 +30,6 @@ import GenericModal from 'components/generic_modal'; import {PostPreviewMetadata} from '@mattermost/types/posts'; import {getSiteURL} from '../../utils/url'; -import * as Utils from '../../utils/utils'; import ForwardPostChannelSelect, {ChannelOption, makeSelectedChannelOption} from './forward_post_channel_select'; import ForwardPostCommentInput from './forward_post_comment_input'; @@ -49,7 +50,7 @@ const ForwardPostModal = ({onExited, post, actions}: Props) => { const channel = useSelector((state: GlobalState) => getChannel(state, {id: post.channel_id})); const currentTeam = useSelector(getCurrentTeam); - const relativePermaLink = useSelector((state: GlobalState) => Utils.getPermalinkURL(state, currentTeam.id, post.id)); + const relativePermaLink = useSelector((state: GlobalState) => getPermalinkURL(state, currentTeam.id, post.id)); const permaLink = `${getSiteURL()}${relativePermaLink}`; const isPrivateConversation = channel.type !== Constants.OPEN_CHANNEL; diff --git a/webapp/channels/src/components/global_header/center_controls/global_search_nav/global_search_nav.tsx b/webapp/channels/src/components/global_header/center_controls/global_search_nav/global_search_nav.tsx index a3bcbd7155..39e0c62870 100644 --- a/webapp/channels/src/components/global_header/center_controls/global_search_nav/global_search_nav.tsx +++ b/webapp/channels/src/components/global_header/center_controls/global_search_nav/global_search_nav.tsx @@ -17,7 +17,7 @@ import { Constants, RHSStates, } from 'utils/constants'; -import * as Utils from 'utils/utils'; +import * as Keyboard from 'utils/keyboard'; const GlobalSearchNav = (): JSX.Element => { const dispatch = useDispatch(); @@ -25,8 +25,8 @@ const GlobalSearchNav = (): JSX.Element => { useEffect(() => { const handleShortcut = (e: KeyboardEvent) => { - if (Utils.cmdOrCtrlPressed(e) && e.shiftKey) { - if (Utils.isKeyPressed(e, Constants.KeyCodes.M)) { + if (Keyboard.cmdOrCtrlPressed(e) && e.shiftKey) { + if (Keyboard.isKeyPressed(e, Constants.KeyCodes.M)) { e.preventDefault(); if (rhsState === RHSStates.MENTION) { dispatch(closeRightHandSide()); diff --git a/webapp/channels/src/components/keyboard_shortcuts/keyboard_shortcuts_modal/keyboard_shortcuts_modal.tsx b/webapp/channels/src/components/keyboard_shortcuts/keyboard_shortcuts_modal/keyboard_shortcuts_modal.tsx index b65030dd15..0db9b68ae6 100644 --- a/webapp/channels/src/components/keyboard_shortcuts/keyboard_shortcuts_modal/keyboard_shortcuts_modal.tsx +++ b/webapp/channels/src/components/keyboard_shortcuts/keyboard_shortcuts_modal/keyboard_shortcuts_modal.tsx @@ -11,7 +11,7 @@ import {GlobalState} from 'types/store'; import {suitePluginIds} from 'utils/constants'; import {t} from 'utils/i18n'; -import * as Utils from 'utils/utils'; +import * as UserAgent from 'utils/user_agent'; import KeyboardShortcutSequence, { KEYBOARD_SHORTCUTS, @@ -91,7 +91,7 @@ const KeyboardShortcutsModal = ({onExited}: Props): JSX.Element => { const handleHide = useCallback(() => setShow(false), []); - const isLinux = Utils.isLinux(); + const isLinux = UserAgent.isLinux(); const isCallsEnabled = useSelector((state: GlobalState) => { return Boolean(state.plugins.plugins[suitePluginIds.calls]); diff --git a/webapp/channels/src/components/keyboard_shortcuts/keyboard_shortcuts_sequence/keyboard_shortcuts_sequence.tsx b/webapp/channels/src/components/keyboard_shortcuts/keyboard_shortcuts_sequence/keyboard_shortcuts_sequence.tsx index 21488238da..c5315e36cb 100644 --- a/webapp/channels/src/components/keyboard_shortcuts/keyboard_shortcuts_sequence/keyboard_shortcuts_sequence.tsx +++ b/webapp/channels/src/components/keyboard_shortcuts/keyboard_shortcuts_sequence/keyboard_shortcuts_sequence.tsx @@ -6,7 +6,7 @@ import React, {memo} from 'react'; import {useIntl} from 'react-intl'; import {ShortcutKeyVariant, ShortcutKey} from 'components/shortcut_key'; -import {isMac} from 'utils/utils'; +import {isMac} from 'utils/user_agent'; import {isMessageDescriptor, KeyboardShortcutDescriptor} from './keyboard_shortcuts'; diff --git a/webapp/channels/src/components/leave_team_modal/leave_team_modal.tsx b/webapp/channels/src/components/leave_team_modal/leave_team_modal.tsx index 27db2843a3..b745a3e125 100644 --- a/webapp/channels/src/components/leave_team_modal/leave_team_modal.tsx +++ b/webapp/channels/src/components/leave_team_modal/leave_team_modal.tsx @@ -12,8 +12,7 @@ import * as UserUtils from 'mattermost-redux/utils/user_utils'; import FormattedMarkdownMessage from 'components/formatted_markdown_message'; import Constants from 'utils/constants'; - -import {isKeyPressed} from 'utils/utils'; +import {isKeyPressed} from 'utils/keyboard'; type Props = { currentUser: UserProfile; diff --git a/webapp/channels/src/components/logged_in/index.ts b/webapp/channels/src/components/logged_in/index.ts index 1b377dc75a..01a2a6b17a 100644 --- a/webapp/channels/src/components/logged_in/index.ts +++ b/webapp/channels/src/components/logged_in/index.ts @@ -16,9 +16,10 @@ import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels' import {getLicense, getConfig} from 'mattermost-redux/selectors/entities/general'; import {getCurrentUser, shouldShowTermsOfService} from 'mattermost-redux/selectors/entities/users'; +import {getChannelURL} from 'selectors/urls'; + import {getHistory} from 'utils/browser_history'; import {checkIfMFARequired} from 'utils/route'; -import {getChannelURL} from 'utils/utils'; import {isPermalinkURL} from 'utils/url'; import LoggedIn from './logged_in'; diff --git a/webapp/channels/src/components/main_menu/main_menu.tsx b/webapp/channels/src/components/main_menu/main_menu.tsx index 6b42d9c87d..7999bb7128 100644 --- a/webapp/channels/src/components/main_menu/main_menu.tsx +++ b/webapp/channels/src/components/main_menu/main_menu.tsx @@ -9,7 +9,7 @@ import {Permissions} from 'mattermost-redux/constants'; import * as GlobalActions from 'actions/global_actions'; import {FREEMIUM_TO_ENTERPRISE_TRIAL_LENGTH_DAYS} from 'utils/cloud_utils'; import {Constants, LicenseSkus, ModalIdentifiers, MattermostFeatures} from 'utils/constants'; -import {cmdOrCtrlPressed, isKeyPressed} from 'utils/utils'; +import {cmdOrCtrlPressed, isKeyPressed} from 'utils/keyboard'; import {makeUrlSafe} from 'utils/url'; import * as UserAgent from 'utils/user_agent'; import InvitationModal from 'components/invitation_modal'; diff --git a/webapp/channels/src/components/menu/menu.tsx b/webapp/channels/src/components/menu/menu.tsx index 9e10805fbb..d41703c2a4 100644 --- a/webapp/channels/src/components/menu/menu.tsx +++ b/webapp/channels/src/components/menu/menu.tsx @@ -20,7 +20,7 @@ import {getIsMobileView} from 'selectors/views/browser'; import {openModal, closeModal} from 'actions/views/modals'; import Constants, {A11yClassNames} from 'utils/constants'; -import {isKeyPressed} from 'utils/utils'; +import {isKeyPressed} from 'utils/keyboard'; import CompassDesignProvider from 'components/compass_design_provider'; import Tooltip from 'components/tooltip'; diff --git a/webapp/channels/src/components/menu/menu_item.tsx b/webapp/channels/src/components/menu/menu_item.tsx index 78d75d3324..157afe2a9b 100644 --- a/webapp/channels/src/components/menu/menu_item.tsx +++ b/webapp/channels/src/components/menu/menu_item.tsx @@ -7,7 +7,7 @@ import MuiMenuItem from '@mui/material/MenuItem'; import type {MenuItemProps as MuiMenuItemProps} from '@mui/material/MenuItem'; import Constants from 'utils/constants'; -import {isKeyPressed} from 'utils/utils'; +import {isKeyPressed} from 'utils/keyboard'; export interface Props extends MuiMenuItemProps { diff --git a/webapp/channels/src/components/menu/sub_menu.tsx b/webapp/channels/src/components/menu/sub_menu.tsx index b63260f9e2..0a5f2061d1 100644 --- a/webapp/channels/src/components/menu/sub_menu.tsx +++ b/webapp/channels/src/components/menu/sub_menu.tsx @@ -14,7 +14,7 @@ import {isAnyModalOpen} from 'selectors/views/modals'; import {openModal, closeModal} from 'actions/views/modals'; import Constants, {A11yClassNames} from 'utils/constants'; -import {isKeyPressed} from 'utils/utils'; +import {isKeyPressed} from 'utils/keyboard'; import CompassDesignProvider from 'components/compass_design_provider'; import GenericModal from 'components/generic_modal'; diff --git a/webapp/channels/src/components/mfa/confirm.tsx b/webapp/channels/src/components/mfa/confirm.tsx index 54932f3cc0..b912d65ae4 100644 --- a/webapp/channels/src/components/mfa/confirm.tsx +++ b/webapp/channels/src/components/mfa/confirm.tsx @@ -5,7 +5,7 @@ import React from 'react'; import {FormattedMessage} from 'react-intl'; import Constants from 'utils/constants'; -import {isKeyPressed} from 'utils/utils'; +import {isKeyPressed} from 'utils/keyboard'; import {redirectUserToDefaultTeam} from 'actions/global_actions'; diff --git a/webapp/channels/src/components/multiselect/multiselect_list.tsx b/webapp/channels/src/components/multiselect/multiselect_list.tsx index 4ca33a2b49..cc86ac97e3 100644 --- a/webapp/channels/src/components/multiselect/multiselect_list.tsx +++ b/webapp/channels/src/components/multiselect/multiselect_list.tsx @@ -8,7 +8,7 @@ import {getOptionValue} from 'react-select/src/builtins'; import {FormattedMessage} from 'react-intl'; import Constants from 'utils/constants'; -import {cmdOrCtrlPressed} from 'utils/utils'; +import {cmdOrCtrlPressed} from 'utils/keyboard'; import LoadingScreen from 'components/loading_screen'; diff --git a/webapp/channels/src/components/new_replies_banner/new_replies_banner.tsx b/webapp/channels/src/components/new_replies_banner/new_replies_banner.tsx index 49ff0f69d6..fcf9726300 100644 --- a/webapp/channels/src/components/new_replies_banner/new_replies_banner.tsx +++ b/webapp/channels/src/components/new_replies_banner/new_replies_banner.tsx @@ -5,7 +5,7 @@ import React, {memo, useEffect, useCallback} from 'react'; import {FormattedMessage, useIntl} from 'react-intl'; import Constants from 'utils/constants'; -import {isKeyPressed} from 'utils/utils'; +import {isKeyPressed} from 'utils/keyboard'; import Toast from 'components/toast/toast'; diff --git a/webapp/channels/src/components/post_reminder_custom_time_picker_modal/post_reminder_custom_time_picker_modal.tsx b/webapp/channels/src/components/post_reminder_custom_time_picker_modal/post_reminder_custom_time_picker_modal.tsx index bfb12cb0b0..6e73b8889e 100644 --- a/webapp/channels/src/components/post_reminder_custom_time_picker_modal/post_reminder_custom_time_picker_modal.tsx +++ b/webapp/channels/src/components/post_reminder_custom_time_picker_modal/post_reminder_custom_time_picker_modal.tsx @@ -6,7 +6,8 @@ import {FormattedMessage} from 'react-intl'; import {Moment} from 'moment-timezone'; import GenericModal from 'components/generic_modal'; -import {isKeyPressed, localizeMessage} from 'utils/utils'; +import {isKeyPressed} from 'utils/keyboard'; +import {localizeMessage} from 'utils/utils'; import DateTimeInput, {getRoundedTime} from 'components/custom_status/date_time_input'; import {toUTCUnix} from 'utils/datetime'; diff --git a/webapp/channels/src/components/profile_popover/profile_popover.tsx b/webapp/channels/src/components/profile_popover/profile_popover.tsx index 286cf45bba..8b2b687634 100644 --- a/webapp/channels/src/components/profile_popover/profile_popover.tsx +++ b/webapp/channels/src/components/profile_popover/profile_popover.tsx @@ -20,6 +20,7 @@ import {ModalData} from 'types/actions'; import {getHistory} from 'utils/browser_history'; import Constants, {A11yClassNames, A11yCustomEventTypes, A11yFocusEventDetail, ModalIdentifiers, UserStatuses} from 'utils/constants'; import {t} from 'utils/i18n'; +import * as Keyboard from 'utils/keyboard'; import * as Utils from 'utils/utils'; import {shouldFocusMainTextbox} from 'utils/post_utils'; @@ -338,7 +339,7 @@ class ProfilePopover extends React.PureComponent { if (shouldFocusMainTextbox(e, document.activeElement)) { this.props.hide?.(); - } else if (Utils.isKeyPressed(e, Constants.KeyCodes.ESCAPE)) { + } else if (Keyboard.isKeyPressed(e, Constants.KeyCodes.ESCAPE)) { this.returnFocus(); } }; diff --git a/webapp/channels/src/components/search/search.tsx b/webapp/channels/src/components/search/search.tsx index 8b40d8300a..45b71dc13b 100644 --- a/webapp/channels/src/components/search/search.tsx +++ b/webapp/channels/src/components/search/search.tsx @@ -11,7 +11,7 @@ import {getCurrentChannelNameForSearchShortcut} from 'mattermost-redux/selectors import {isServerVersionGreaterThanOrEqualTo} from 'utils/server_version'; import {isDesktopApp, getDesktopVersion, isMacApp} from 'utils/user_agent'; import Constants, {searchHintOptions, RHSStates, searchFilesHintOptions} from 'utils/constants'; -import * as Utils from 'utils/utils'; +import * as Keyboard from 'utils/keyboard'; import HeaderIconWrapper from 'components/channel_header/components/header_icon_wrapper'; import SearchHint from 'components/search_hint/search_hint'; @@ -109,7 +109,7 @@ const Search: React.FC = (props: Props): JSX.Element => { } const handleKeyDown = (e: KeyboardEvent) => { - if (Utils.cmdOrCtrlPressed(e) && Utils.isKeyPressed(e, Constants.KeyCodes.F)) { + if (Keyboard.cmdOrCtrlPressed(e) && Keyboard.isKeyPressed(e, Constants.KeyCodes.F)) { if (!isDesktop && !e.shiftKey) { return; } diff --git a/webapp/channels/src/components/search_bar/search_bar.tsx b/webapp/channels/src/components/search_bar/search_bar.tsx index 78f23e70a7..706ee5cded 100644 --- a/webapp/channels/src/components/search_bar/search_bar.tsx +++ b/webapp/channels/src/components/search_bar/search_bar.tsx @@ -6,7 +6,7 @@ import classNames from 'classnames'; import {FormattedMessage, useIntl} from 'react-intl'; import Constants from 'utils/constants'; -import * as Utils from 'utils/utils'; +import * as Keyboard from 'utils/keyboard'; import SuggestionDate from 'components/suggestion/suggestion_date'; import SearchSuggestionList from 'components/suggestion/search_suggestion_list'; @@ -71,27 +71,27 @@ const SearchBar: React.FunctionComponent = (props: Props): JSX.Element => }, [searchTerms]); const handleKeyDown = (e: ChangeEvent): void => { - if (Utils.isKeyPressed(e as any, KeyCodes.ESCAPE)) { + if (Keyboard.isKeyPressed(e as any, KeyCodes.ESCAPE)) { searchRef.current?.blur(); e.stopPropagation(); e.preventDefault(); } - if (Utils.isKeyPressed(e as any, KeyCodes.DOWN)) { + if (Keyboard.isKeyPressed(e as any, KeyCodes.DOWN)) { e.preventDefault(); props.updateHighlightedSearchHint(1, true); } - if (Utils.isKeyPressed(e as any, KeyCodes.UP)) { + if (Keyboard.isKeyPressed(e as any, KeyCodes.UP)) { e.preventDefault(); props.updateHighlightedSearchHint(-1, true); } - if (Utils.isKeyPressed(e as any, KeyCodes.ENTER)) { + if (Keyboard.isKeyPressed(e as any, KeyCodes.ENTER)) { props.handleEnterKey(e); } - if (Utils.isKeyPressed(e as any, KeyCodes.BACKSPACE) && !searchTerms) { + if (Keyboard.isKeyPressed(e as any, KeyCodes.BACKSPACE) && !searchTerms) { if (props.clearSearchType) { props.clearSearchType(); } diff --git a/webapp/channels/src/components/search_results/messages_or_files_selector.tsx b/webapp/channels/src/components/search_results/messages_or_files_selector.tsx index d65833c8cc..dbd1bbddbb 100644 --- a/webapp/channels/src/components/search_results/messages_or_files_selector.tsx +++ b/webapp/channels/src/components/search_results/messages_or_files_selector.tsx @@ -7,7 +7,7 @@ import {FormattedMessage} from 'react-intl'; import {SearchFilterType} from '../search/types'; import {SearchType} from 'types/store/rhs'; -import * as Utils from 'utils/utils'; +import * as Keyboard from 'utils/keyboard'; import Constants from 'utils/constants'; import FilesFilterMenu from './files_filter_menu'; @@ -32,7 +32,7 @@ export default function MessagesOrFilesSelector(props: Props): JSX.Element {
diff --git a/webapp/channels/src/components/sidebar/sidebar.tsx b/webapp/channels/src/components/sidebar/sidebar.tsx index 6473d9607c..30f2e72a61 100644 --- a/webapp/channels/src/components/sidebar/sidebar.tsx +++ b/webapp/channels/src/components/sidebar/sidebar.tsx @@ -19,6 +19,7 @@ import {ModalData} from 'types/actions'; import {RhsState} from 'types/store/rhs'; import Constants, {ModalIdentifiers, RHSStates} from 'utils/constants'; +import * as Keyboard from 'utils/keyboard'; import * as Utils from 'utils/utils'; import CreateUserGroupsModal from 'components/create_user_groups_modal'; @@ -98,15 +99,15 @@ export default class Sidebar extends React.PureComponent { }; handleKeyDownEvent = (event: KeyboardEvent) => { - if (Utils.isKeyPressed(event, Constants.KeyCodes.ESCAPE)) { + if (Keyboard.isKeyPressed(event, Constants.KeyCodes.ESCAPE)) { this.props.actions.clearChannelSelection(); return; } - const ctrlOrMetaKeyPressed = Utils.cmdOrCtrlPressed(event, true); + const ctrlOrMetaKeyPressed = Keyboard.cmdOrCtrlPressed(event, true); if (ctrlOrMetaKeyPressed) { - if (Utils.isKeyPressed(event, Constants.KeyCodes.FORWARD_SLASH)) { + if (Keyboard.isKeyPressed(event, Constants.KeyCodes.FORWARD_SLASH)) { event.preventDefault(); if (this.props.isKeyBoardShortcutModalOpen) { this.props.actions.closeModal(ModalIdentifiers.KEYBOARD_SHORTCUTS_MODAL); @@ -116,7 +117,7 @@ export default class Sidebar extends React.PureComponent { dialogType: KeyboardShortcutsModal, }); } - } else if (Utils.isKeyPressed(event, Constants.KeyCodes.A) && event.shiftKey) { + } else if (Keyboard.isKeyPressed(event, Constants.KeyCodes.A) && event.shiftKey) { event.preventDefault(); this.props.actions.openModal({ diff --git a/webapp/channels/src/components/sidebar/sidebar_category/sidebar_category.tsx b/webapp/channels/src/components/sidebar/sidebar_category/sidebar_category.tsx index 579341205f..a150e7005e 100644 --- a/webapp/channels/src/components/sidebar/sidebar_category/sidebar_category.tsx +++ b/webapp/channels/src/components/sidebar/sidebar_category/sidebar_category.tsx @@ -16,7 +16,7 @@ import Tooltip from 'components/tooltip'; import {DraggingState} from 'types/store'; import Constants, {A11yCustomEventTypes, DraggingStateTypes, DraggingStates, Preferences, Touched} from 'utils/constants'; import {t} from 'utils/i18n'; -import {isKeyPressed} from 'utils/utils'; +import {isKeyPressed} from 'utils/keyboard'; import SidebarChannel from '../sidebar_channel'; import {SidebarCategoryHeader} from '../sidebar_category_header'; import InviteMembersButton from '../invite_members_button'; diff --git a/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_channel_link/sidebar_channel_link.tsx b/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_channel_link/sidebar_channel_link.tsx index 471d4aa329..6c41780368 100644 --- a/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_channel_link/sidebar_channel_link.tsx +++ b/webapp/channels/src/components/sidebar/sidebar_channel/sidebar_channel_link/sidebar_channel_link.tsx @@ -15,8 +15,9 @@ import Tooltip from 'components/tooltip'; import Constants, {RHSStates} from 'utils/constants'; import {wrapEmojis} from 'utils/emoji_utils'; +import {cmdOrCtrlPressed} from 'utils/keyboard'; import {isDesktopApp} from 'utils/user_agent'; -import {cmdOrCtrlPressed, localizeMessage} from 'utils/utils'; +import {localizeMessage} from 'utils/utils'; import {ChannelsAndDirectMessagesTour} from 'components/tours/onboarding_tour'; import CustomStatusEmoji from 'components/custom_status/custom_status_emoji'; diff --git a/webapp/channels/src/components/sidebar/sidebar_list/sidebar_list.tsx b/webapp/channels/src/components/sidebar/sidebar_list/sidebar_list.tsx index e07152ce42..02eac11ace 100644 --- a/webapp/channels/src/components/sidebar/sidebar_list/sidebar_list.tsx +++ b/webapp/channels/src/components/sidebar/sidebar_list/sidebar_list.tsx @@ -17,6 +17,7 @@ import {General} from 'mattermost-redux/constants'; import {trackEvent} from 'actions/telemetry_actions'; import {DraggingState} from 'types/store'; import {Constants, DraggingStates, DraggingStateTypes} from 'utils/constants'; +import * as Keyboard from 'utils/keyboard'; import * as Utils from 'utils/utils'; import {StaticPage} from 'types/store/lhs'; @@ -314,7 +315,7 @@ export default class SidebarList extends React.PureComponent { }; navigateChannelShortcut = (e: KeyboardEvent) => { - if (e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey && (Utils.isKeyPressed(e, Constants.KeyCodes.UP) || Utils.isKeyPressed(e, Constants.KeyCodes.DOWN))) { + if (e.altKey && !e.shiftKey && !e.ctrlKey && !e.metaKey && (Keyboard.isKeyPressed(e, Constants.KeyCodes.UP) || Keyboard.isKeyPressed(e, Constants.KeyCodes.DOWN))) { e.preventDefault(); const staticPageIds = this.getDisplayedStaticPageIds(); @@ -324,7 +325,7 @@ export default class SidebarList extends React.PureComponent { const curIndex = allIds.indexOf(curSelectedId); let nextIndex; - if (Utils.isKeyPressed(e, Constants.KeyCodes.DOWN)) { + if (Keyboard.isKeyPressed(e, Constants.KeyCodes.DOWN)) { nextIndex = curIndex + 1; } else { nextIndex = curIndex - 1; @@ -335,13 +336,13 @@ export default class SidebarList extends React.PureComponent { if (nextIndex >= staticPageIds.length) { this.scrollToChannel(nextId); } - } else if (Utils.cmdOrCtrlPressed(e) && e.shiftKey && Utils.isKeyPressed(e, Constants.KeyCodes.K)) { + } else if (Keyboard.cmdOrCtrlPressed(e) && e.shiftKey && Keyboard.isKeyPressed(e, Constants.KeyCodes.K)) { this.props.handleOpenMoreDirectChannelsModal(e); } }; navigateUnreadChannelShortcut = (e: KeyboardEvent) => { - if (e.altKey && e.shiftKey && !e.ctrlKey && !e.metaKey && (Utils.isKeyPressed(e, Constants.KeyCodes.UP) || Utils.isKeyPressed(e, Constants.KeyCodes.DOWN))) { + if (e.altKey && e.shiftKey && !e.ctrlKey && !e.metaKey && (Keyboard.isKeyPressed(e, Constants.KeyCodes.UP) || Keyboard.isKeyPressed(e, Constants.KeyCodes.DOWN))) { e.preventDefault(); const allChannelIds = this.getDisplayedChannelIds(); @@ -356,7 +357,7 @@ export default class SidebarList extends React.PureComponent { } let direction = 0; - if (Utils.isKeyPressed(e, Constants.KeyCodes.UP)) { + if (Keyboard.isKeyPressed(e, Constants.KeyCodes.UP)) { direction = -1; } else { direction = 1; diff --git a/webapp/channels/src/components/sidebar_right/sidebar_right.tsx b/webapp/channels/src/components/sidebar_right/sidebar_right.tsx index 69dbab0702..5399e083a3 100644 --- a/webapp/channels/src/components/sidebar_right/sidebar_right.tsx +++ b/webapp/channels/src/components/sidebar_right/sidebar_right.tsx @@ -13,7 +13,8 @@ import {RhsState} from 'types/store/rhs'; import {trackEvent} from 'actions/telemetry_actions.jsx'; import Constants from 'utils/constants'; -import {isMac, cmdOrCtrlPressed, isKeyPressed} from 'utils/utils'; +import {isMac} from 'utils/user_agent'; +import {cmdOrCtrlPressed, isKeyPressed} from 'utils/keyboard'; import FileUploadOverlay from 'components/file_upload_overlay'; import RhsThread from 'components/rhs_thread'; diff --git a/webapp/channels/src/components/suggestion/command_provider/app_command_parser/app_command_parser_dependencies.ts b/webapp/channels/src/components/suggestion/command_provider/app_command_parser/app_command_parser_dependencies.ts index bb26212fb4..08a5a61e3a 100644 --- a/webapp/channels/src/components/suggestion/command_provider/app_command_parser/app_command_parser_dependencies.ts +++ b/webapp/channels/src/components/suggestion/command_provider/app_command_parser/app_command_parser_dependencies.ts @@ -75,10 +75,8 @@ export { filterEmptyOptions, } from 'utils/apps'; -import { - isMac, - localizeAndFormatMessage, -} from 'utils/utils'; +import {isMac} from 'utils/user_agent'; +import {localizeAndFormatMessage} from 'utils/utils'; export type Store = { dispatch: DispatchFunc; diff --git a/webapp/channels/src/components/suggestion/command_provider/command_provider.tsx b/webapp/channels/src/components/suggestion/command_provider/command_provider.tsx index 0a5e73b6f2..81388a97d5 100644 --- a/webapp/channels/src/components/suggestion/command_provider/command_provider.tsx +++ b/webapp/channels/src/components/suggestion/command_provider/command_provider.tsx @@ -14,7 +14,6 @@ import {AutocompleteSuggestion, CommandArgs} from '@mattermost/types/integration import globalStore from 'stores/redux_store'; import * as UserAgent from 'utils/user_agent'; -import * as Utils from 'utils/utils'; import {Constants} from 'utils/constants'; import Suggestion from '../suggestion'; @@ -229,7 +228,7 @@ export default class CommandProvider extends Provider { let matches: AutocompleteSuggestion[] = []; let cmd = 'Ctrl'; - if (Utils.isMac()) { + if (UserAgent.isMac()) { cmd = '⌘'; } diff --git a/webapp/channels/src/components/suggestion/search_date_suggestion/search_date_suggestion.tsx b/webapp/channels/src/components/suggestion/search_date_suggestion/search_date_suggestion.tsx index ab6ecf6f38..0de0c3239c 100644 --- a/webapp/channels/src/components/suggestion/search_date_suggestion/search_date_suggestion.tsx +++ b/webapp/channels/src/components/suggestion/search_date_suggestion/search_date_suggestion.tsx @@ -9,6 +9,7 @@ import type {Locale} from 'date-fns'; import Suggestion from '../suggestion.jsx'; +import * as Keyboard from 'utils/keyboard'; import * as Utils from 'utils/utils'; import Constants from 'utils/constants'; @@ -27,9 +28,9 @@ export default class SearchDateSuggestion extends Suggestion { }; handleKeyDown = (e: KeyboardEvent) => { - if (Utils.isKeyPressed(e, Constants.KeyCodes.DOWN) && document.activeElement?.id === 'searchBox') { + if (Keyboard.isKeyPressed(e, Constants.KeyCodes.DOWN) && document.activeElement?.id === 'searchBox') { this.setState({datePickerFocused: true}); - } else if (Utils.isKeyPressed(e, Constants.KeyCodes.ESCAPE)) { + } else if (Keyboard.isKeyPressed(e, Constants.KeyCodes.ESCAPE)) { this.props.handleEscape(); } }; diff --git a/webapp/channels/src/components/suggestion/suggestion_box/suggestion_box.jsx b/webapp/channels/src/components/suggestion/suggestion_box/suggestion_box.jsx index 73cc071e88..71456f1512 100644 --- a/webapp/channels/src/components/suggestion/suggestion_box/suggestion_box.jsx +++ b/webapp/channels/src/components/suggestion/suggestion_box/suggestion_box.jsx @@ -8,6 +8,7 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter'; import QuickInput from 'components/quick_input'; import Constants, {A11yCustomEventTypes} from 'utils/constants'; +import * as Keyboard from 'utils/keyboard'; import * as UserAgent from 'utils/user_agent'; import * as Utils from 'utils/utils'; @@ -497,7 +498,7 @@ export default class SuggestionBox extends React.PureComponent { if (finish && this.props.onKeyPress) { let ke = e; - if (!e || Utils.isKeyPressed(e, Constants.KeyCodes.TAB)) { + if (!e || Keyboard.isKeyPressed(e, Constants.KeyCodes.TAB)) { ke = new KeyboardEvent('keydown', { bubbles: true, cancelable: true, keyCode: 13, }); @@ -582,13 +583,13 @@ export default class SuggestionBox extends React.PureComponent { handleKeyDown = (e) => { if ((this.props.openWhenEmpty || this.props.value) && this.hasSuggestions()) { const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey; - if (Utils.isKeyPressed(e, KeyCodes.UP)) { + if (Keyboard.isKeyPressed(e, KeyCodes.UP)) { this.selectPrevious(); e.preventDefault(); - } else if (Utils.isKeyPressed(e, KeyCodes.DOWN)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.DOWN)) { this.selectNext(); e.preventDefault(); - } else if ((Utils.isKeyPressed(e, KeyCodes.ENTER) && !ctrlOrMetaKeyPressed) || (this.props.completeOnTab && Utils.isKeyPressed(e, KeyCodes.TAB))) { + } else if ((Keyboard.isKeyPressed(e, KeyCodes.ENTER) && !ctrlOrMetaKeyPressed) || (this.props.completeOnTab && Keyboard.isKeyPressed(e, KeyCodes.TAB))) { let matchedPretext = ''; for (let i = 0; i < this.state.terms.length; i++) { if (this.state.terms[i] === this.state.selection) { @@ -611,7 +612,7 @@ export default class SuggestionBox extends React.PureComponent { this.props.onKeyDown(e); } e.preventDefault(); - } else if (Utils.isKeyPressed(e, KeyCodes.ESCAPE)) { + } else if (Keyboard.isKeyPressed(e, KeyCodes.ESCAPE)) { this.clear(); this.setState({presentationType: 'text'}); e.preventDefault(); diff --git a/webapp/channels/src/components/team_controller/team_controller.tsx b/webapp/channels/src/components/team_controller/team_controller.tsx index 256f9d8d13..a520697214 100644 --- a/webapp/channels/src/components/team_controller/team_controller.tsx +++ b/webapp/channels/src/components/team_controller/team_controller.tsx @@ -10,8 +10,8 @@ import {ActionResult} from 'mattermost-redux/types/actions'; import {reconnect} from 'actions/websocket_actions.jsx'; import Constants from 'utils/constants'; +import {cmdOrCtrlPressed, isKeyPressed} from 'utils/keyboard'; import {isIosSafari} from 'utils/user_agent'; -import {cmdOrCtrlPressed, isKeyPressed} from 'utils/utils'; import {makeAsyncComponent} from 'components/async_load'; import ChannelController from 'components/channel_layout/channel_controller'; diff --git a/webapp/channels/src/components/team_sidebar/team_sidebar.tsx b/webapp/channels/src/components/team_sidebar/team_sidebar.tsx index cbe9f90113..2abafac9d3 100644 --- a/webapp/channels/src/components/team_sidebar/team_sidebar.tsx +++ b/webapp/channels/src/components/team_sidebar/team_sidebar.tsx @@ -13,6 +13,7 @@ import {Team} from '@mattermost/types/teams'; import Permissions from 'mattermost-redux/constants/permissions'; import {Constants} from 'utils/constants'; +import * as Keyboard from 'utils/keyboard'; import {filterAndSortTeamsByDisplayName} from 'utils/team_utils'; import * as Utils from 'utils/utils'; @@ -71,9 +72,9 @@ export default class TeamSidebar extends React.PureComponent { } switchToPrevOrNextTeam = (e: KeyboardEvent, currentTeamId: string, teams: Team[]) => { - if (Utils.isKeyPressed(e, Constants.KeyCodes.UP) || Utils.isKeyPressed(e, Constants.KeyCodes.DOWN)) { + if (Keyboard.isKeyPressed(e, Constants.KeyCodes.UP) || Keyboard.isKeyPressed(e, Constants.KeyCodes.DOWN)) { e.preventDefault(); - const delta = Utils.isKeyPressed(e, Constants.KeyCodes.DOWN) ? 1 : -1; + const delta = Keyboard.isKeyPressed(e, Constants.KeyCodes.DOWN) ? 1 : -1; const pos = teams.findIndex((team: Team) => team.id === currentTeamId); const newPos = pos + delta; @@ -107,7 +108,7 @@ export default class TeamSidebar extends React.PureComponent { ]; for (const idx in digits) { - if (Utils.isKeyPressed(e, digits[idx]) && parseInt(idx, 10) < teams.length) { + if (Keyboard.isKeyPressed(e, digits[idx]) && parseInt(idx, 10) < teams.length) { e.preventDefault(); // prevents reloading the current team, while still capturing the keyboard shortcut diff --git a/webapp/channels/src/components/threading/global_threads/thread_list/thread_list.tsx b/webapp/channels/src/components/threading/global_threads/thread_list/thread_list.tsx index 06a3d7dd7b..c49571f1c9 100644 --- a/webapp/channels/src/components/threading/global_threads/thread_list/thread_list.tsx +++ b/webapp/channels/src/components/threading/global_threads/thread_list/thread_list.tsx @@ -8,7 +8,7 @@ import {isEmpty} from 'lodash'; import {PlaylistCheckIcon} from '@mattermost/compass-icons/components'; -import * as Utils from 'utils/utils'; +import * as Keyboard from 'utils/keyboard'; import {getThreadCountsInCurrentTeam} from 'mattermost-redux/selectors/entities/threads'; import {getThreads, markAllThreadsInTeamRead} from 'mattermost-redux/actions/threads'; import {trackEvent} from 'actions/telemetry_actions'; @@ -80,7 +80,7 @@ const ThreadList = ({ return; } const comboKeyPressed = e.altKey || e.metaKey || e.shiftKey || e.ctrlKey; - if (comboKeyPressed || (!Utils.isKeyPressed(e, Constants.KeyCodes.DOWN) && !Utils.isKeyPressed(e, Constants.KeyCodes.UP))) { + if (comboKeyPressed || (!Keyboard.isKeyPressed(e, Constants.KeyCodes.DOWN) && !Keyboard.isKeyPressed(e, Constants.KeyCodes.UP))) { return; } @@ -94,7 +94,7 @@ const ThreadList = ({ let threadIdToSelect = 0; if (selectedThreadId) { const selectedThreadIndex = data.indexOf(selectedThreadId); - if (Utils.isKeyPressed(e, Constants.KeyCodes.DOWN)) { + if (Keyboard.isKeyPressed(e, Constants.KeyCodes.DOWN)) { if (selectedThreadIndex < data.length - 1) { threadIdToSelect = selectedThreadIndex + 1; } @@ -104,7 +104,7 @@ const ThreadList = ({ } } - if (Utils.isKeyPressed(e, Constants.KeyCodes.UP)) { + if (Keyboard.isKeyPressed(e, Constants.KeyCodes.UP)) { if (selectedThreadIndex > 0) { threadIdToSelect = selectedThreadIndex - 1; } else { diff --git a/webapp/channels/src/components/toast_wrapper/toast_wrapper.tsx b/webapp/channels/src/components/toast_wrapper/toast_wrapper.tsx index 45f0033ffa..11b37d203d 100644 --- a/webapp/channels/src/components/toast_wrapper/toast_wrapper.tsx +++ b/webapp/channels/src/components/toast_wrapper/toast_wrapper.tsx @@ -7,8 +7,9 @@ import {RouteComponentProps} from 'react-router-dom'; import {Preferences} from 'mattermost-redux/constants'; +import {isKeyPressed} from 'utils/keyboard'; import {isIdNotPost, getNewMessageIndex} from 'utils/post_utils'; -import {isKeyPressed, localizeMessage} from 'utils/utils'; +import {localizeMessage} from 'utils/utils'; import {isToday} from 'utils/datetime'; import Constants from 'utils/constants'; import {getHistory} from 'utils/browser_history'; diff --git a/webapp/channels/src/components/tours/crt_tour/collapsed_reply_threads_modal/collapsed_reply_threads_modal.tsx b/webapp/channels/src/components/tours/crt_tour/collapsed_reply_threads_modal/collapsed_reply_threads_modal.tsx index 8f529e9df0..106cccae55 100644 --- a/webapp/channels/src/components/tours/crt_tour/collapsed_reply_threads_modal/collapsed_reply_threads_modal.tsx +++ b/webapp/channels/src/components/tours/crt_tour/collapsed_reply_threads_modal/collapsed_reply_threads_modal.tsx @@ -13,7 +13,7 @@ import GenericModal from 'components/generic_modal'; import NextIcon from 'components/widgets/icons/fa_next_icon'; import FormattedMarkdownMessage from 'components/formatted_markdown_message'; import {Constants, ModalIdentifiers, Preferences} from 'utils/constants'; -import * as Utils from 'utils/utils'; +import * as Keyboard from 'utils/keyboard'; import './collapsed_reply_threads_modal.scss'; import {AutoTourStatus, TTNameMapToATStatusKey, TutorialTourName} from '../../constant'; @@ -26,7 +26,7 @@ function CollapsedReplyThreadsModal(props: Props) { const dispatch = useDispatch(); const currentUserId = useSelector(getCurrentUserId); const handleKeyDown = useCallback((e: KeyboardEvent) => { - if (Utils.isKeyPressed(e, Constants.KeyCodes.ENTER)) { + if (Keyboard.isKeyPressed(e, Constants.KeyCodes.ENTER)) { onNext(); } }, []); diff --git a/webapp/channels/src/components/update_user_group_modal/update_user_group_modal.tsx b/webapp/channels/src/components/update_user_group_modal/update_user_group_modal.tsx index aeae2f4969..e82b9c802f 100644 --- a/webapp/channels/src/components/update_user_group_modal/update_user_group_modal.tsx +++ b/webapp/channels/src/components/update_user_group_modal/update_user_group_modal.tsx @@ -7,6 +7,7 @@ import {Modal} from 'react-bootstrap'; import {FormattedMessage} from 'react-intl'; +import * as Keyboard from 'utils/keyboard'; import * as Utils from 'utils/utils'; import {CustomGroupPatch, Group} from '@mattermost/types/groups'; @@ -52,7 +53,7 @@ const UpdateUserGroupModal = (props: Props) => { }, [name, mention, hasUpdated, saving]); const handleKeyDown = useCallback((e: KeyboardEvent) => { - if (Utils.isKeyPressed(e, Constants.KeyCodes.ENTER) && isSaveEnabled()) { + if (Keyboard.isKeyPressed(e, Constants.KeyCodes.ENTER) && isSaveEnabled()) { patchGroup(); } }, [name, mention, hasUpdated, saving]); diff --git a/webapp/channels/src/components/user_group_popover/user_group_popover.tsx b/webapp/channels/src/components/user_group_popover/user_group_popover.tsx index e543e9ff86..523df8f013 100644 --- a/webapp/channels/src/components/user_group_popover/user_group_popover.tsx +++ b/webapp/channels/src/components/user_group_popover/user_group_popover.tsx @@ -14,7 +14,7 @@ import {Group} from '@mattermost/types/groups'; import {ActionResult} from 'mattermost-redux/types/actions'; import {shouldFocusMainTextbox} from 'utils/post_utils'; -import * as Utils from 'utils/utils'; +import * as Keyboard from 'utils/keyboard'; import Constants, {A11yClassNames, A11yCustomEventTypes, A11yFocusEventDetail, ModalIdentifiers} from 'utils/constants'; import {QuickInput} from 'components/quick_input/quick_input'; @@ -169,7 +169,7 @@ const UserGroupPopover = (props: Props) => { const handleKeyDown = (e: React.KeyboardEvent) => { if (shouldFocusMainTextbox(e, document.activeElement)) { hide(); - } else if (Utils.isKeyPressed(e, Constants.KeyCodes.ESCAPE)) { + } else if (Keyboard.isKeyPressed(e, Constants.KeyCodes.ESCAPE)) { returnFocus(); } }; diff --git a/webapp/channels/src/components/user_settings/advanced/user_settings_advanced.test.tsx b/webapp/channels/src/components/user_settings/advanced/user_settings_advanced.test.tsx index 03854f35ef..9ef4be1ef2 100644 --- a/webapp/channels/src/components/user_settings/advanced/user_settings_advanced.test.tsx +++ b/webapp/channels/src/components/user_settings/advanced/user_settings_advanced.test.tsx @@ -8,10 +8,10 @@ import AdvancedSettingsDisplay from 'components/user_settings/advanced/user_sett import {Preferences} from 'utils/constants'; import {TestHelper} from 'utils/test_helper'; -import {isMac} from 'utils/utils'; +import {isMac} from 'utils/user_agent'; jest.mock('actions/global_actions'); -jest.mock('utils/utils'); +jest.mock('utils/user_agent'); describe('components/user_settings/display/UserSettingsDisplay', () => { const user = TestHelper.getUserMock({ diff --git a/webapp/channels/src/components/user_settings/advanced/user_settings_advanced.tsx b/webapp/channels/src/components/user_settings/advanced/user_settings_advanced.tsx index 660ca1e8ae..c4dc2b556e 100644 --- a/webapp/channels/src/components/user_settings/advanced/user_settings_advanced.tsx +++ b/webapp/channels/src/components/user_settings/advanced/user_settings_advanced.tsx @@ -10,7 +10,8 @@ import {emitUserLoggedOutEvent} from 'actions/global_actions'; import Constants, {AdvancedSections, Preferences} from 'utils/constants'; import {t} from 'utils/i18n'; -import {a11yFocus, isMac, localizeMessage} from 'utils/utils'; +import {isMac} from 'utils/user_agent'; +import {a11yFocus, localizeMessage} from 'utils/utils'; import SettingItemMax from 'components/setting_item_max'; import ConfirmModal from 'components/confirm_modal'; diff --git a/webapp/channels/src/components/user_settings/display/manage_languages/manage_languages.tsx b/webapp/channels/src/components/user_settings/display/manage_languages/manage_languages.tsx index 98c30d1668..1ed2d04de9 100644 --- a/webapp/channels/src/components/user_settings/display/manage_languages/manage_languages.tsx +++ b/webapp/channels/src/components/user_settings/display/manage_languages/manage_languages.tsx @@ -10,7 +10,7 @@ import SettingItemMax from 'components/setting_item_max'; import {ActionResult} from 'mattermost-redux/types/actions'; import * as I18n from 'i18n/i18n.jsx'; -import {isKeyPressed} from 'utils/utils'; +import {isKeyPressed} from 'utils/keyboard'; import Constants from 'utils/constants'; import {UserProfile} from '@mattermost/types/users'; diff --git a/webapp/channels/src/components/user_settings/modal/user_settings_modal.tsx b/webapp/channels/src/components/user_settings/modal/user_settings_modal.tsx index 1c0fc6f932..bdba21b1e6 100644 --- a/webapp/channels/src/components/user_settings/modal/user_settings_modal.tsx +++ b/webapp/channels/src/components/user_settings/modal/user_settings_modal.tsx @@ -15,6 +15,7 @@ import {UserProfile} from '@mattermost/types/users'; import {StatusOK} from '@mattermost/types/client4'; import store from 'stores/redux_store.jsx'; import Constants from 'utils/constants'; +import * as Keyboard from 'utils/keyboard'; import * as Utils from 'utils/utils'; import {t} from 'utils/i18n'; import ConfirmModal from 'components/confirm_modal'; @@ -146,7 +147,7 @@ class UserSettingsModal extends React.PureComponent { } handleKeyDown = (e: KeyboardEvent) => { - if (Utils.cmdOrCtrlPressed(e) && e.shiftKey && Utils.isKeyPressed(e, Constants.KeyCodes.A)) { + if (Keyboard.cmdOrCtrlPressed(e) && e.shiftKey && Keyboard.isKeyPressed(e, Constants.KeyCodes.A)) { e.preventDefault(); this.handleHide(); } diff --git a/webapp/channels/src/components/user_settings/notifications/desktop_notification_setting/desktop_notification_settings.test.tsx b/webapp/channels/src/components/user_settings/notifications/desktop_notification_setting/desktop_notification_settings.test.tsx index d703d781ed..722e917ac2 100644 --- a/webapp/channels/src/components/user_settings/notifications/desktop_notification_setting/desktop_notification_settings.test.tsx +++ b/webapp/channels/src/components/user_settings/notifications/desktop_notification_setting/desktop_notification_settings.test.tsx @@ -8,8 +8,8 @@ import {NotificationLevels} from 'utils/constants'; import DesktopNotificationSettings from './desktop_notification_settings'; -jest.mock('utils/utils', () => { - const original = jest.requireActual('utils/utils'); +jest.mock('utils/notification_sounds', () => { + const original = jest.requireActual('utils/notification_sounds'); return { ...original, hasSoundOptions: jest.fn(() => true), diff --git a/webapp/channels/src/components/user_settings/notifications/desktop_notification_setting/desktop_notification_settings.tsx b/webapp/channels/src/components/user_settings/notifications/desktop_notification_setting/desktop_notification_settings.tsx index 9c0d3eefa2..56a7e711ba 100644 --- a/webapp/channels/src/components/user_settings/notifications/desktop_notification_setting/desktop_notification_settings.tsx +++ b/webapp/channels/src/components/user_settings/notifications/desktop_notification_setting/desktop_notification_settings.tsx @@ -8,6 +8,7 @@ import {FormattedMessage} from 'react-intl'; import semver from 'semver'; import {NotificationLevels} from 'utils/constants'; +import * as NotificationSounds from 'utils/notification_sounds'; import * as Utils from 'utils/utils'; import {t} from 'utils/i18n'; import {isDesktopApp} from 'utils/user_agent'; @@ -86,7 +87,7 @@ export default class DesktopNotificationSettings extends React.PureComponent { return {value: sound, label: sound}; }); @@ -144,7 +145,7 @@ export default class DesktopNotificationSettings extends React.PureComponent @@ -344,7 +345,7 @@ export default class DesktopNotificationSettings extends React.PureComponent { let formattedMessageProps; - const hasSoundOption = Utils.hasSoundOptions(); + const hasSoundOption = NotificationSounds.hasSoundOptions(); if (this.props.activity === NotificationLevels.MENTION) { if (hasSoundOption && this.props.sound !== 'false') { formattedMessageProps = { diff --git a/webapp/channels/src/components/user_settings/security/user_access_token_section/user_access_token_section.tsx b/webapp/channels/src/components/user_settings/security/user_access_token_section/user_access_token_section.tsx index 8305f05080..261cbe2bd1 100644 --- a/webapp/channels/src/components/user_settings/security/user_access_token_section/user_access_token_section.tsx +++ b/webapp/channels/src/components/user_settings/security/user_access_token_section/user_access_token_section.tsx @@ -8,6 +8,7 @@ import * as UserUtils from 'mattermost-redux/utils/user_utils'; import {trackEvent} from 'actions/telemetry_actions.jsx'; import Constants from 'utils/constants'; +import * as Keyboard from 'utils/keyboard'; import {isMobile} from 'utils/user_agent'; import * as Utils from 'utils/utils'; import ConfirmModal from 'components/confirm_modal'; @@ -263,7 +264,7 @@ export default class UserAccessTokenSection extends React.PureComponent { - if (Utils.isKeyPressed(e, Constants.KeyCodes.ENTER)) { + if (Keyboard.isKeyPressed(e, Constants.KeyCodes.ENTER)) { this.confirmCreateToken(); } }; diff --git a/webapp/channels/src/components/widgets/menu/menu_items/submenu_item.tsx b/webapp/channels/src/components/widgets/menu/menu_items/submenu_item.tsx index 3f3308aedc..3e9b58eef1 100644 --- a/webapp/channels/src/components/widgets/menu/menu_items/submenu_item.tsx +++ b/webapp/channels/src/components/widgets/menu/menu_items/submenu_item.tsx @@ -4,6 +4,7 @@ import React, {CSSProperties} from 'react'; import classNames from 'classnames'; +import * as Keyboard from 'utils/keyboard'; import * as Utils from 'utils/utils'; import {showMobileSubMenuModal} from 'actions/global_actions'; @@ -113,7 +114,7 @@ export default class SubMenuItem extends React.PureComponent { }; handleKeyDown = (event: React.KeyboardEvent) => { - if (Utils.isKeyPressed(event, Constants.KeyCodes.ENTER)) { + if (Keyboard.isKeyPressed(event, Constants.KeyCodes.ENTER)) { if (this.props.action) { this.onClick(event); } else { @@ -121,7 +122,7 @@ export default class SubMenuItem extends React.PureComponent { } } - if (Utils.isKeyPressed(event, Constants.KeyCodes.RIGHT)) { + if (Keyboard.isKeyPressed(event, Constants.KeyCodes.RIGHT)) { if (this.props.direction === 'right') { this.show(); } else { @@ -129,7 +130,7 @@ export default class SubMenuItem extends React.PureComponent { } } - if (Utils.isKeyPressed(event, Constants.KeyCodes.LEFT)) { + if (Keyboard.isKeyPressed(event, Constants.KeyCodes.LEFT)) { if (this.props.direction === 'left') { this.show(); } else { diff --git a/webapp/channels/src/selectors/urls.ts b/webapp/channels/src/selectors/urls.ts new file mode 100644 index 0000000000..02773eec18 --- /dev/null +++ b/webapp/channels/src/selectors/urls.ts @@ -0,0 +1,53 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import {Post} from '@mattermost/types/posts'; +import {Channel} from '@mattermost/types/channels'; +import {Team} from '@mattermost/types/teams'; + +import {getRedirectChannelNameForTeam} from 'mattermost-redux/selectors/entities/channels'; +import { + getCurrentRelativeTeamUrl, + getCurrentTeam, + getCurrentTeamId, + getTeam, +} from 'mattermost-redux/selectors/entities/teams'; + +import {GlobalState} from 'types/store'; + +import Constants from 'utils/constants'; + +function getTeamRelativeUrl(team: Team | undefined) { + if (!team) { + return ''; + } + + return '/' + team.name; +} + +export function getPermalinkURL(state: GlobalState, teamId: Team['id'], postId: Post['id']): string { + let team = getTeam(state, teamId); + if (!team) { + team = getCurrentTeam(state); + } + return `${getTeamRelativeUrl(team)}/pl/${postId}`; +} + +export function getChannelURL(state: GlobalState, channel: Channel, teamId: string): string { + let notificationURL; + if (channel && (channel.type === Constants.DM_CHANNEL || channel.type === Constants.GM_CHANNEL)) { + notificationURL = getCurrentRelativeTeamUrl(state) + '/channels/' + channel.name; + } else if (channel) { + const team = getTeam(state, teamId); + notificationURL = getTeamRelativeUrl(team) + '/channels/' + channel.name; + } else if (teamId) { + const team = getTeam(state, teamId); + const redirectChannel = getRedirectChannelNameForTeam(state, teamId); + notificationURL = getTeamRelativeUrl(team) + `/channels/${redirectChannel}`; + } else { + const currentTeamId = getCurrentTeamId(state); + const redirectChannel = getRedirectChannelNameForTeam(state, currentTeamId); + notificationURL = getCurrentRelativeTeamUrl(state) + `/channels/${redirectChannel}`; + } + return notificationURL; +} diff --git a/webapp/channels/src/utils/a11y_controller.ts b/webapp/channels/src/utils/a11y_controller.ts index 404c721106..96b655df72 100644 --- a/webapp/channels/src/utils/a11y_controller.ts +++ b/webapp/channels/src/utils/a11y_controller.ts @@ -2,8 +2,8 @@ // See LICENSE.txt for license information. import Constants, {EventTypes, A11yClassNames, A11yAttributeNames, A11yCustomEventTypes, isA11yFocusEventDetail} from 'utils/constants'; -import {isKeyPressed, cmdOrCtrlPressed, isMac} from 'utils/utils'; -import {isDesktopApp} from 'utils/user_agent'; +import {isKeyPressed, cmdOrCtrlPressed} from 'utils/keyboard'; +import {isDesktopApp, isMac} from 'utils/user_agent'; const listenerOptions = { capture: true, diff --git a/webapp/channels/src/utils/keyboard.test.ts b/webapp/channels/src/utils/keyboard.test.ts new file mode 100644 index 0000000000..e72c93c598 --- /dev/null +++ b/webapp/channels/src/utils/keyboard.test.ts @@ -0,0 +1,145 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import * as Keyboard from './keyboard'; + +describe('isKeyPressed', () => { + test('Key match is used over keyCode if it exists', () => { + for (const data of [ + { + event: new KeyboardEvent('keydown', {key: '/', keyCode: 55}), + key: ['/', 191], + valid: true, + }, + { + event: new KeyboardEvent('keydown', {key: 'ù', keyCode: 191}), + key: ['/', 191], + valid: true, + }, + ]) { + expect(Keyboard.isKeyPressed(data.event, data.key as [string, number])).toEqual(data.valid); + } + }); + + test('Key match works for both uppercase and lower case', () => { + for (const data of [ + { + event: new KeyboardEvent('keydown', {key: 'A', keyCode: 65, code: 'KeyA'}), + key: ['a', 65], + valid: true, + }, + { + event: new KeyboardEvent('keydown', {key: 'a', keyCode: 65, code: 'KeyA'}), + key: ['a', 65], + valid: true, + }, + ]) { + expect(Keyboard.isKeyPressed(data.event, data.key as [string, number])).toEqual(data.valid); + } + }); + + test('KeyCode is used for dead letter keys', () => { + for (const data of [ + { + event: new KeyboardEvent('keydown', {key: 'Dead', keyCode: 222}), + key: ['', 222], + valid: true, + }, + { + event: new KeyboardEvent('keydown', {key: 'Dead', keyCode: 222}), + key: ['not-used-field', 222], + valid: true, + }, + { + event: new KeyboardEvent('keydown', {key: 'Dead', keyCode: 222}), + key: [null, 222], + valid: true, + }, + { + event: new KeyboardEvent('keydown', {key: 'Dead', keyCode: 222}), + key: [null, 223], + valid: false, + }, + ]) { + expect(Keyboard.isKeyPressed(data.event, data.key as [string, number])).toEqual(data.valid); + } + }); + + test('KeyCode is used for unidentified keys', () => { + for (const data of [ + { + event: new KeyboardEvent('keydown', {key: 'Unidentified', keyCode: 2220, code: 'Unidentified'}), + key: ['', 2220], + valid: true, + }, + { + event: new KeyboardEvent('keydown', {key: 'Unidentified', keyCode: 2220, code: 'Unidentified'}), + key: ['not-used-field', 2220], + valid: true, + }, + { + event: new KeyboardEvent('keydown', {key: 'Unidentified', keyCode: 2220, code: 'Unidentified'}), + key: [null, 2220], + valid: true, + }, + { + event: new KeyboardEvent('keydown', {key: 'Unidentified', keyCode: 2220, code: 'Unidentified'}), + key: [null, 2221], + valid: false, + }, + ]) { + expect(Keyboard.isKeyPressed(data.event, data.key as [string, number])).toEqual(data.valid); + } + }); + + test('KeyCode is used for undefined keys', () => { + for (const data of [ + { + event: {keyCode: 2221}, + key: ['', 2221], + valid: true, + }, + { + event: {keyCode: 2221}, + key: ['not-used-field', 2221], + valid: true, + }, + { + event: {keyCode: 2221}, + key: [null, 2221], + valid: true, + }, + { + event: {keyCode: 2221}, + key: [null, 2222], + valid: false, + }, + ]) { + expect(Keyboard.isKeyPressed(data.event as KeyboardEvent, data.key as [string, number])).toEqual(data.valid); + } + }); + + test('keyCode is used for determining if it exists', () => { + for (const data of [ + { + event: {key: 'a', keyCode: 65}, + key: ['k', 65], + valid: true, + }, + { + event: {key: 'b', keyCode: 66}, + key: ['y', 66], + valid: true, + }, + ]) { + expect(Keyboard.isKeyPressed(data.event as KeyboardEvent, data.key as [string, number])).toEqual(data.valid); + } + }); + + test('key should be tested as fallback for different layout of english keyboards', () => { + //key will be k for keyboards like dvorak but code will be keyV as `v` is pressed + const event = {key: 'k', code: 'KeyV'}; + const key: [string, number] = ['k', 2221]; + expect(Keyboard.isKeyPressed(event as KeyboardEvent, key)).toEqual(true); + }); +}); diff --git a/webapp/channels/src/utils/keyboard.ts b/webapp/channels/src/utils/keyboard.ts new file mode 100644 index 0000000000..4a978d44db --- /dev/null +++ b/webapp/channels/src/utils/keyboard.ts @@ -0,0 +1,35 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import Constants from 'utils/constants'; +import * as UserAgent from 'utils/user_agent'; + +export function cmdOrCtrlPressed(e: React.KeyboardEvent | KeyboardEvent, allowAlt = false) { + const isMac = UserAgent.isMac(); + + if (allowAlt) { + return (isMac && e.metaKey) || (!isMac && e.ctrlKey); + } + return (isMac && e.metaKey) || (!isMac && e.ctrlKey && !e.altKey); +} + +export function isKeyPressed(event: React.KeyboardEvent | KeyboardEvent, key: [string, number]) { + // There are two types of keyboards + // 1. English with different layouts(Ex: Dvorak) + // 2. Different language keyboards(Ex: Russian) + + if (event.keyCode === Constants.KeyCodes.COMPOSING[1]) { + return false; + } + + // checks for event.key for older browsers and also for the case of different English layout keyboards. + if (typeof event.key !== 'undefined' && event.key !== 'Unidentified' && event.key !== 'Dead') { + const isPressedByCode = event.key === key[0] || event.key === key[0].toUpperCase(); + if (isPressedByCode) { + return true; + } + } + + // used for different language keyboards to detect the position of keys + return event.keyCode === key[1]; +} diff --git a/webapp/channels/src/utils/notification_sounds.ts b/webapp/channels/src/utils/notification_sounds.ts new file mode 100644 index 0000000000..b2f8cf0e24 --- /dev/null +++ b/webapp/channels/src/utils/notification_sounds.ts @@ -0,0 +1,40 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import bing from 'sounds/bing.mp3'; +import crackle from 'sounds/crackle.mp3'; +import down from 'sounds/down.mp3'; +import hello from 'sounds/hello.mp3'; +import ripple from 'sounds/ripple.mp3'; +import upstairs from 'sounds/upstairs.mp3'; + +import * as UserAgent from 'utils/user_agent'; + +export const notificationSounds = new Map([ + ['Bing', bing], + ['Crackle', crackle], + ['Down', down], + ['Hello', hello], + ['Ripple', ripple], + ['Upstairs', upstairs], +]); + +let canDing = true; +export function ding(name: string) { + if (hasSoundOptions() && canDing) { + tryNotificationSound(name); + canDing = false; + setTimeout(() => { + canDing = true; + }, 3000); + } +} + +export function tryNotificationSound(name: string) { + const audio = new Audio(notificationSounds.get(name) ?? notificationSounds.get('Bing')); + audio.play(); +} + +export function hasSoundOptions() { + return (!UserAgent.isEdge()); +} diff --git a/webapp/channels/src/utils/post_utils.ts b/webapp/channels/src/utils/post_utils.ts index 07e613d45e..fea73c028d 100644 --- a/webapp/channels/src/utils/post_utils.ts +++ b/webapp/channels/src/utils/post_utils.ts @@ -42,11 +42,11 @@ import {getIsMobileView} from 'selectors/views/browser'; import {GlobalState} from 'types/store'; import Constants, {PostListRowListIds, Preferences} from 'utils/constants'; +import * as Keyboard from 'utils/keyboard'; import {formatWithRenderer} from 'utils/markdown'; import MentionableRenderer from 'utils/markdown/mentionable_renderer'; import {allAtMentions} from 'utils/text_formatting'; import {isMobile} from 'utils/user_agent'; -import * as Utils from 'utils/utils'; import EmojiMap from './emoji_map'; import * as Emoticons from './emoticons'; @@ -236,7 +236,7 @@ export function shouldFocusMainTextbox(e: React.KeyboardEvent | KeyboardEvent, a } // Focus if it is an attempted paste - if (Utils.cmdOrCtrlPressed(e) && Utils.isKeyPressed(e, Constants.KeyCodes.V)) { + if (Keyboard.cmdOrCtrlPressed(e) && Keyboard.isKeyPressed(e, Constants.KeyCodes.V)) { return true; } @@ -257,7 +257,7 @@ export function shouldFocusMainTextbox(e: React.KeyboardEvent | KeyboardEvent, a // Do not focus when pressing space on link elements const spaceKeepFocusTags = ['BUTTON', 'A']; - if (Utils.isKeyPressed(e, Constants.KeyCodes.SPACE) && spaceKeepFocusTags.includes(activeElement.tagName)) { + if (Keyboard.isKeyPressed(e, Constants.KeyCodes.SPACE) && spaceKeepFocusTags.includes(activeElement.tagName)) { return false; } @@ -311,7 +311,7 @@ export function postMessageOnKeyPress( } // Only ENTER sends, unless shift or alt key pressed. - if (!Utils.isKeyPressed(event, Constants.KeyCodes.ENTER) || event.shiftKey || event.altKey) { + if (!Keyboard.isKeyPressed(event, Constants.KeyCodes.ENTER) || event.shiftKey || event.altKey) { return {allowSending: false}; } diff --git a/webapp/channels/src/utils/user_agent.tsx b/webapp/channels/src/utils/user_agent.tsx index 6a25b2d3b2..11f61f7676 100644 --- a/webapp/channels/src/utils/user_agent.tsx +++ b/webapp/channels/src/utils/user_agent.tsx @@ -142,6 +142,10 @@ export function isMac(): boolean { return userAgent().indexOf('Macintosh') !== -1; } +export function isLinux(): boolean { + return navigator.platform.toUpperCase().indexOf('LINUX') >= 0; +} + export function isWindows7(): boolean { const appVersion = navigator.appVersion; diff --git a/webapp/channels/src/utils/utils.test.tsx b/webapp/channels/src/utils/utils.test.tsx index 56bcea6a5e..f271a282f2 100644 --- a/webapp/channels/src/utils/utils.test.tsx +++ b/webapp/channels/src/utils/utils.test.tsx @@ -292,147 +292,6 @@ describe('Utils.isValidUsername', () => { }); }); -describe('Utils.isKeyPressed', () => { - test('Key match is used over keyCode if it exists', () => { - for (const data of [ - { - event: new KeyboardEvent('keydown', {key: '/', keyCode: 55}), - key: ['/', 191], - valid: true, - }, - { - event: new KeyboardEvent('keydown', {key: 'ù', keyCode: 191}), - key: ['/', 191], - valid: true, - }, - ]) { - expect(Utils.isKeyPressed(data.event, data.key as [string, number])).toEqual(data.valid); - } - }); - - test('Key match works for both uppercase and lower case', () => { - for (const data of [ - { - event: new KeyboardEvent('keydown', {key: 'A', keyCode: 65, code: 'KeyA'}), - key: ['a', 65], - valid: true, - }, - { - event: new KeyboardEvent('keydown', {key: 'a', keyCode: 65, code: 'KeyA'}), - key: ['a', 65], - valid: true, - }, - ]) { - expect(Utils.isKeyPressed(data.event, data.key as [string, number])).toEqual(data.valid); - } - }); - - test('KeyCode is used for dead letter keys', () => { - for (const data of [ - { - event: new KeyboardEvent('keydown', {key: 'Dead', keyCode: 222}), - key: ['', 222], - valid: true, - }, - { - event: new KeyboardEvent('keydown', {key: 'Dead', keyCode: 222}), - key: ['not-used-field', 222], - valid: true, - }, - { - event: new KeyboardEvent('keydown', {key: 'Dead', keyCode: 222}), - key: [null, 222], - valid: true, - }, - { - event: new KeyboardEvent('keydown', {key: 'Dead', keyCode: 222}), - key: [null, 223], - valid: false, - }, - ]) { - expect(Utils.isKeyPressed(data.event, data.key as [string, number])).toEqual(data.valid); - } - }); - - test('KeyCode is used for unidentified keys', () => { - for (const data of [ - { - event: new KeyboardEvent('keydown', {key: 'Unidentified', keyCode: 2220, code: 'Unidentified'}), - key: ['', 2220], - valid: true, - }, - { - event: new KeyboardEvent('keydown', {key: 'Unidentified', keyCode: 2220, code: 'Unidentified'}), - key: ['not-used-field', 2220], - valid: true, - }, - { - event: new KeyboardEvent('keydown', {key: 'Unidentified', keyCode: 2220, code: 'Unidentified'}), - key: [null, 2220], - valid: true, - }, - { - event: new KeyboardEvent('keydown', {key: 'Unidentified', keyCode: 2220, code: 'Unidentified'}), - key: [null, 2221], - valid: false, - }, - ]) { - expect(Utils.isKeyPressed(data.event, data.key as [string, number])).toEqual(data.valid); - } - }); - - test('KeyCode is used for undefined keys', () => { - for (const data of [ - { - event: {keyCode: 2221}, - key: ['', 2221], - valid: true, - }, - { - event: {keyCode: 2221}, - key: ['not-used-field', 2221], - valid: true, - }, - { - event: {keyCode: 2221}, - key: [null, 2221], - valid: true, - }, - { - event: {keyCode: 2221}, - key: [null, 2222], - valid: false, - }, - ]) { - expect(Utils.isKeyPressed(data.event as KeyboardEvent, data.key as [string, number])).toEqual(data.valid); - } - }); - - test('keyCode is used for determining if it exists', () => { - for (const data of [ - { - event: {key: 'a', keyCode: 65}, - key: ['k', 65], - valid: true, - }, - { - event: {key: 'b', keyCode: 66}, - key: ['y', 66], - valid: true, - }, - ]) { - expect(Utils.isKeyPressed(data.event as KeyboardEvent, data.key as [string, number])).toEqual(data.valid); - } - }); - - test('key should be tested as fallback for different layout of english keyboards', () => { - //key will be k for keyboards like dvorak but code will be keyV as `v` is pressed - const event = {key: 'k', code: 'KeyV'}; - const key: [string, number] = ['k', 2221]; - expect(Utils.isKeyPressed(event as KeyboardEvent, key)).toEqual(true); - }); -}); - describe('Utils.localizeMessage', () => { const originalGetState = store.getState; diff --git a/webapp/channels/src/utils/utils.tsx b/webapp/channels/src/utils/utils.tsx index 9f71b304d5..10f5c6283e 100644 --- a/webapp/channels/src/utils/utils.tsx +++ b/webapp/channels/src/utils/utils.tsx @@ -1,8 +1,6 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -/* eslint-disable max-lines */ - import React, {LinkHTMLAttributes} from 'react'; import {FormattedMessage, IntlShape} from 'react-intl'; @@ -30,7 +28,6 @@ import { getChannel, getChannelsNameMapInTeam, getMyChannelMemberships, - getRedirectChannelNameForTeam, } from 'mattermost-redux/selectors/entities/channels'; import {getPost} from 'mattermost-redux/selectors/entities/posts'; import {getBool, getTeammateNameDisplaySetting, Theme, isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences'; @@ -38,10 +35,6 @@ import {getCurrentUser, getCurrentUserId, isFirstAdmin} from 'mattermost-redux/s import {blendColors, changeOpacity} from 'mattermost-redux/utils/theme_utils'; import {displayUsername, isSystemAdmin} from 'mattermost-redux/utils/user_utils'; import { - getCurrentRelativeTeamUrl, - getCurrentTeam, - getCurrentTeamId, - getTeam, getTeamByName, getTeamMemberships, isTeamSameWithCurrentTeam, @@ -50,14 +43,9 @@ import { import {addUserToTeam} from 'actions/team_actions'; import {searchForTerm} from 'actions/post_actions'; import {getHistory} from 'utils/browser_history'; +import * as Keyboard from 'utils/keyboard'; import * as UserAgent from 'utils/user_agent'; import {isDesktopApp} from 'utils/user_agent'; -import bing from 'sounds/bing.mp3'; -import crackle from 'sounds/crackle.mp3'; -import down from 'sounds/down.mp3'; -import hello from 'sounds/hello.mp3'; -import ripple from 'sounds/ripple.mp3'; -import upstairs from 'sounds/upstairs.mp3'; import {t} from 'utils/i18n'; import store from 'stores/redux_store.jsx'; @@ -108,14 +96,6 @@ export enum TimeInformation { export type TimeUnit = Exclude; export type TimeDirection = TimeInformation.FUTURE | TimeInformation.PAST; -export function isMac() { - return navigator.platform.toUpperCase().indexOf('MAC') >= 0; -} - -export function isLinux() { - return navigator.platform.toUpperCase().indexOf('LINUX') >= 0; -} - export function createSafeId(prop: {props: {defaultMessage: string}} | string): string | undefined { let str = ''; @@ -128,42 +108,14 @@ export function createSafeId(prop: {props: {defaultMessage: string}} | string): return str.replace(new RegExp(' ', 'g'), '_'); } -export function cmdOrCtrlPressed(e: React.KeyboardEvent | KeyboardEvent, allowAlt = false) { - if (allowAlt) { - return (isMac() && e.metaKey) || (!isMac() && e.ctrlKey); - } - return (isMac() && e.metaKey) || (!isMac() && e.ctrlKey && !e.altKey); -} - -export function isKeyPressed(event: React.KeyboardEvent | KeyboardEvent, key: [string, number]) { - // There are two types of keyboards - // 1. English with different layouts(Ex: Dvorak) - // 2. Different language keyboards(Ex: Russian) - - if (event.keyCode === Constants.KeyCodes.COMPOSING[1]) { - return false; - } - - // checks for event.key for older browsers and also for the case of different English layout keyboards. - if (typeof event.key !== 'undefined' && event.key !== 'Unidentified' && event.key !== 'Dead') { - const isPressedByCode = event.key === key[0] || event.key === key[0].toUpperCase(); - if (isPressedByCode) { - return true; - } - } - - // used for different language keyboards to detect the position of keys - return event.keyCode === key[1]; -} - /** * check keydown event for line break combo. Should catch alt/option + enter not all browsers except Safari */ export function isUnhandledLineBreakKeyCombo(e: React.KeyboardEvent | KeyboardEvent): boolean { return Boolean( - isKeyPressed(e, Constants.KeyCodes.ENTER) && + Keyboard.isKeyPressed(e, Constants.KeyCodes.ENTER) && !e.shiftKey && // shift + enter is already handled everywhere, so don't handle again - (e.altKey && !UserAgent.isSafari() && !cmdOrCtrlPressed(e)), // alt/option + enter is already handled in Safari, so don't handle again + (e.altKey && !UserAgent.isSafari() && !Keyboard.cmdOrCtrlPressed(e)), // alt/option + enter is already handled in Safari, so don't handle again ); } @@ -186,83 +138,6 @@ export function insertLineBreakFromKeyEvent(e: React.KeyboardEvent { - canDing = true; - }, 3000); - } -} - -export function tryNotificationSound(name: string) { - const audio = new Audio(notificationSounds.get(name) ?? notificationSounds.get('Bing')); - audio.play(); -} - -export function hasSoundOptions() { - return (!UserAgent.isEdge()); -} - export function getDateForUnixTicks(ticks: number): Date { return new Date(ticks); }