MM-49393 Begin splitting up utils.tsx (#22983)

* Add selectors/urls to Channels

* Create utils/notification_sounds in Channels

* Move Utils.isLinux to UserAgent.isLinux

* Remove Utils.isMac in favour of UserAgent.isMac

* Move cmdOrCtrlPressed and isKeyPressed into new utils/keyboard

* Remove eslint-disable for max-lines in utils.tsx
Этот коммит содержится в:
Harrison Healey
2023-04-17 15:10:15 -04:00
коммит произвёл GitHub
родитель 941ee8509a
Коммит 3f022e728f
76 изменённых файлов: 508 добавлений и 491 удалений

Просмотреть файл

@@ -14,9 +14,11 @@ import {isSystemMessage, isUserAddedInChannel} from 'mattermost-redux/utils/post
import {displayUsername} from 'mattermost-redux/utils/user_utils'; import {displayUsername} from 'mattermost-redux/utils/user_utils';
import {isThreadOpen} from 'selectors/views/threads'; import {isThreadOpen} from 'selectors/views/threads';
import {getChannelURL, getPermalinkURL} from 'selectors/urls';
import {getHistory} from 'utils/browser_history'; import {getHistory} from 'utils/browser_history';
import Constants, {NotificationLevels, UserStatuses} from 'utils/constants'; import Constants, {NotificationLevels, UserStatuses} from 'utils/constants';
import * as NotificationSounds from 'utils/notification_sounds';
import {showNotification} from 'utils/notifications'; import {showNotification} from 'utils/notifications';
import {isDesktopApp, isMobileApp, isWindowsApp} from 'utils/user_agent'; import {isDesktopApp, isMobileApp, isWindowsApp} from 'utils/user_agent';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
@@ -178,17 +180,17 @@ export function sendDesktopNotification(post, msgProps) {
if (notify) { if (notify) {
const updatedState = getState(); const updatedState = getState();
let url = Utils.getChannelURL(updatedState, channel, teamId); let url = getChannelURL(updatedState, channel, teamId);
if (isCrtReply) { if (isCrtReply) {
url = Utils.getPermalinkURL(updatedState, teamId, post.id); url = getPermalinkURL(updatedState, teamId, post.id);
} }
dispatch(notifyMe(title, body, channel, teamId, !sound, soundName, url)); dispatch(notifyMe(title, body, channel, teamId, !sound, soundName, url));
//Don't add extra sounds on native desktop clients //Don't add extra sounds on native desktop clients
if (sound && !isDesktopApp() && !isMobileApp()) { if (sound && !isDesktopApp() && !isMobileApp()) {
Utils.ding(soundName); NotificationSounds.ding(soundName);
} }
} }
}; };

Просмотреть файл

@@ -5,8 +5,8 @@ import testConfigureStore from 'tests/test_store';
import {getHistory} from 'utils/browser_history'; import {getHistory} from 'utils/browser_history';
import Constants, {NotificationLevels, UserStatuses} from 'utils/constants'; import Constants, {NotificationLevels, UserStatuses} from 'utils/constants';
import * as NotificationSounds from 'utils/notification_sounds';
import * as utils from 'utils/notifications'; import * as utils from 'utils/notifications';
import * as baseUtils from 'utils/utils';
import {sendDesktopNotification} from './notification_actions'; import {sendDesktopNotification} from './notification_actions';
@@ -22,7 +22,7 @@ describe('notification_actions', () => {
beforeEach(() => { beforeEach(() => {
spy = jest.spyOn(utils, 'showNotification'); spy = jest.spyOn(utils, 'showNotification');
baseUtils.ding = jest.fn(); NotificationSounds.ding = jest.fn();
crt = { crt = {
user_id: 'current_user_id', user_id: 'current_user_id',
@@ -315,7 +315,7 @@ describe('notification_actions', () => {
}); });
test('should default sound when no sound is specified', () => { 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'; baseState.entities.users.profiles.current_user_id.notify_props.desktop_sound = 'true';
const store = testConfigureStore(baseState); const store = testConfigureStore(baseState);
return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => { return store.dispatch(sendDesktopNotification(post, msgProps)).then(() => {
@@ -324,7 +324,7 @@ describe('notification_actions', () => {
}); });
test('should use specified sound when specified', () => { 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_sound = 'true';
baseState.entities.users.profiles.current_user_id.notify_props.desktop_notification_sound = 'Crackle'; baseState.entities.users.profiles.current_user_id.notify_props.desktop_notification_sound = 'Crackle';
const store = testConfigureStore(baseState); const store = testConfigureStore(baseState);

Просмотреть файл

@@ -13,6 +13,7 @@ import * as GlobalActions from 'actions/global_actions';
import Constants, {AdvancedTextEditor as AdvancedTextEditorConst, Locations, ModalIdentifiers, Preferences} from 'utils/constants'; import Constants, {AdvancedTextEditor as AdvancedTextEditorConst, Locations, ModalIdentifiers, Preferences} from 'utils/constants';
import {PreferenceType} from '@mattermost/types/preferences'; import {PreferenceType} from '@mattermost/types/preferences';
import * as Keyboard from 'utils/keyboard';
import * as UserAgent from 'utils/user_agent'; import * as UserAgent from 'utils/user_agent';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
import { import {
@@ -819,11 +820,11 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
handleKeyDown = (e: React.KeyboardEvent<TextboxElement>) => { handleKeyDown = (e: React.KeyboardEvent<TextboxElement>) => {
const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey; 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 ctrlKeyCombo = Keyboard.cmdOrCtrlPressed(e) && !e.altKey && !e.shiftKey;
const ctrlAltCombo = Utils.cmdOrCtrlPressed(e, true) && e.altKey; const ctrlAltCombo = Keyboard.cmdOrCtrlPressed(e, true) && e.altKey;
const shiftAltCombo = !Utils.cmdOrCtrlPressed(e) && e.shiftKey && e.altKey; const shiftAltCombo = !Keyboard.cmdOrCtrlPressed(e) && e.shiftKey && e.altKey;
// listen for line break key combo and insert new line character // listen for line break key combo and insert new line character
if (Utils.isUnhandledLineBreakKeyCombo(e)) { if (Utils.isUnhandledLineBreakKeyCombo(e)) {
@@ -838,7 +839,7 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
if ( if (
(this.props.ctrlSend || this.props.codeBlockOnCtrlEnter) && (this.props.ctrlSend || this.props.codeBlockOnCtrlEnter) &&
Utils.isKeyPressed(e, KeyCodes.ENTER) && Keyboard.isKeyPressed(e, KeyCodes.ENTER) &&
(e.ctrlKey || e.metaKey) (e.ctrlKey || e.metaKey)
) { ) {
this.setShowPreview(false); this.setShowPreview(false);
@@ -849,7 +850,7 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
const draft = this.state.draft!; const draft = this.state.draft!;
const {message} = draft; const {message} = draft;
if (Utils.isKeyPressed(e, KeyCodes.ESCAPE)) { if (Keyboard.isKeyPressed(e, KeyCodes.ESCAPE)) {
this.textboxRef.current?.blur(); this.textboxRef.current?.blur();
} }
@@ -858,7 +859,7 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
!e.metaKey && !e.metaKey &&
!e.altKey && !e.altKey &&
!e.shiftKey && !e.shiftKey &&
Utils.isKeyPressed(e, KeyCodes.UP) && Keyboard.isKeyPressed(e, KeyCodes.UP) &&
message === '' message === ''
) { ) {
e.preventDefault(); e.preventDefault();
@@ -879,13 +880,13 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
} = e.target as TextboxElement; } = e.target as TextboxElement;
if (ctrlKeyCombo) { if (ctrlKeyCombo) {
if (Utils.isKeyPressed(e, KeyCodes.UP)) { if (Keyboard.isKeyPressed(e, KeyCodes.UP)) {
e.preventDefault(); e.preventDefault();
this.props.onMoveHistoryIndexBack(); this.props.onMoveHistoryIndexBack();
} else if (Utils.isKeyPressed(e, KeyCodes.DOWN)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.DOWN)) {
e.preventDefault(); e.preventDefault();
this.props.onMoveHistoryIndexForward(); this.props.onMoveHistoryIndexForward();
} else if (Utils.isKeyPressed(e, KeyCodes.B)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.B)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.applyMarkdown({ this.applyMarkdown({
@@ -894,7 +895,7 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
selectionEnd, selectionEnd,
message: value, message: value,
}); });
} else if (Utils.isKeyPressed(e, KeyCodes.I)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.I)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.applyMarkdown({ this.applyMarkdown({
@@ -905,7 +906,7 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
}); });
} }
} else if (ctrlAltCombo) { } else if (ctrlAltCombo) {
if (Utils.isKeyPressed(e, KeyCodes.K)) { if (Keyboard.isKeyPressed(e, KeyCodes.K)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.applyMarkdown({ this.applyMarkdown({
@@ -914,7 +915,7 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
selectionEnd, selectionEnd,
message: value, message: value,
}); });
} else if (Utils.isKeyPressed(e, KeyCodes.C)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.C)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.applyMarkdown({ this.applyMarkdown({
@@ -923,21 +924,21 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
selectionEnd, selectionEnd,
message: value, message: value,
}); });
} else if (Utils.isKeyPressed(e, KeyCodes.E)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.E)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.toggleEmojiPicker(); this.toggleEmojiPicker();
} else if (Utils.isKeyPressed(e, KeyCodes.T)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.T)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.toggleAdvanceTextEditor(); 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.stopPropagation();
e.preventDefault(); e.preventDefault();
this.setShowPreview(!this.props.shouldShowPreview); this.setShowPreview(!this.props.shouldShowPreview);
} }
} else if (shiftAltCombo) { } else if (shiftAltCombo) {
if (Utils.isKeyPressed(e, KeyCodes.X)) { if (Keyboard.isKeyPressed(e, KeyCodes.X)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.applyMarkdown({ this.applyMarkdown({
@@ -946,7 +947,7 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
selectionEnd, selectionEnd,
message: value, message: value,
}); });
} else if (Utils.isKeyPressed(e, KeyCodes.SEVEN)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.SEVEN)) {
e.preventDefault(); e.preventDefault();
this.applyMarkdown({ this.applyMarkdown({
markdownMode: 'ol', markdownMode: 'ol',
@@ -954,7 +955,7 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
selectionEnd, selectionEnd,
message: value, message: value,
}); });
} else if (Utils.isKeyPressed(e, KeyCodes.EIGHT)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.EIGHT)) {
e.preventDefault(); e.preventDefault();
this.applyMarkdown({ this.applyMarkdown({
markdownMode: 'ul', markdownMode: 'ul',
@@ -962,7 +963,7 @@ class AdvancedCreateComment extends React.PureComponent<Props, State> {
selectionEnd, selectionEnd,
message: value, message: value,
}); });
} else if (Utils.isKeyPressed(e, KeyCodes.NINE)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.NINE)) {
e.preventDefault(); e.preventDefault();
this.applyMarkdown({ this.applyMarkdown({
markdownMode: 'quote', markdownMode: 'quote',

Просмотреть файл

@@ -22,6 +22,7 @@ import Constants, {
Preferences, Preferences,
AdvancedTextEditor as AdvancedTextEditorConst, AdvancedTextEditor as AdvancedTextEditorConst,
} from 'utils/constants'; } from 'utils/constants';
import * as Keyboard from 'utils/keyboard';
import { import {
containsAtChannel, containsAtChannel,
specialMentionsInText, specialMentionsInText,
@@ -34,7 +35,6 @@ import {
} from 'utils/post_utils'; } from 'utils/post_utils';
import {getTable, hasHtmlLink, formatMarkdownMessage, formatGithubCodePaste, isGitHubCodeBlock} from 'utils/paste'; import {getTable, hasHtmlLink, formatMarkdownMessage, formatGithubCodePaste, isGitHubCodeBlock} from 'utils/paste';
import * as UserAgent from 'utils/user_agent'; import * as UserAgent from 'utils/user_agent';
import {isMac} from 'utils/utils';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
import EmojiMap from 'utils/emoji_map'; import EmojiMap from 'utils/emoji_map';
import {applyMarkdown, ApplyMarkdownOptions} from 'utils/markdown/apply_markdown'; import {applyMarkdown, ApplyMarkdownOptions} from 'utils/markdown/apply_markdown';
@@ -1096,7 +1096,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
documentKeyHandler = (e: KeyboardEvent) => { documentKeyHandler = (e: KeyboardEvent) => {
const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey; 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) { if (lastMessageReactionKeyCombo) {
this.reactToLastMessage(e); this.reactToLastMessage(e);
return; return;
@@ -1134,12 +1134,12 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey; const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey;
const ctrlEnterKeyCombo = (this.props.ctrlSend || this.props.codeBlockOnCtrlEnter) && const ctrlEnterKeyCombo = (this.props.ctrlSend || this.props.codeBlockOnCtrlEnter) &&
Utils.isKeyPressed(e, KeyCodes.ENTER) && Keyboard.isKeyPressed(e, KeyCodes.ENTER) &&
ctrlOrMetaKeyPressed; ctrlOrMetaKeyPressed;
const ctrlKeyCombo = Utils.cmdOrCtrlPressed(e) && !e.altKey && !e.shiftKey; const ctrlKeyCombo = Keyboard.cmdOrCtrlPressed(e) && !e.altKey && !e.shiftKey;
const ctrlAltCombo = Utils.cmdOrCtrlPressed(e, true) && e.altKey; const ctrlAltCombo = Keyboard.cmdOrCtrlPressed(e, true) && e.altKey;
const shiftAltCombo = !Utils.cmdOrCtrlPressed(e) && e.shiftKey && e.altKey; const shiftAltCombo = !Keyboard.cmdOrCtrlPressed(e) && e.shiftKey && e.altKey;
// listen for line break key combo and insert new line character // listen for line break key combo and insert new line character
if (Utils.isUnhandledLineBreakKeyCombo(e)) { if (Utils.isUnhandledLineBreakKeyCombo(e)) {
@@ -1155,7 +1155,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
const {message} = this.state; const {message} = this.state;
if (Utils.isKeyPressed(e, KeyCodes.ESCAPE)) { if (Keyboard.isKeyPressed(e, KeyCodes.ESCAPE)) {
this.textboxRef.current?.blur(); this.textboxRef.current?.blur();
} }
@@ -1164,7 +1164,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
!e.metaKey && !e.metaKey &&
!e.altKey && !e.altKey &&
!e.shiftKey && !e.shiftKey &&
Utils.isKeyPressed(e, KeyCodes.UP) && Keyboard.isKeyPressed(e, KeyCodes.UP) &&
message === '' message === ''
) { ) {
e.preventDefault(); e.preventDefault();
@@ -1182,15 +1182,15 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
} = e.target as TextboxElement; } = e.target as TextboxElement;
if (ctrlKeyCombo) { if (ctrlKeyCombo) {
if (draftMessageIsEmpty && Utils.isKeyPressed(e, KeyCodes.UP)) { if (draftMessageIsEmpty && Keyboard.isKeyPressed(e, KeyCodes.UP)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.loadPrevMessage(e); this.loadPrevMessage(e);
} else if (draftMessageIsEmpty && Utils.isKeyPressed(e, KeyCodes.DOWN)) { } else if (draftMessageIsEmpty && Keyboard.isKeyPressed(e, KeyCodes.DOWN)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.loadNextMessage(e); this.loadNextMessage(e);
} else if (Utils.isKeyPressed(e, KeyCodes.B)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.B)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.applyMarkdown({ this.applyMarkdown({
@@ -1199,7 +1199,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
selectionEnd, selectionEnd,
message: value, message: value,
}); });
} else if (Utils.isKeyPressed(e, KeyCodes.I)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.I)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.applyMarkdown({ this.applyMarkdown({
@@ -1210,7 +1210,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
}); });
} }
} else if (ctrlAltCombo) { } else if (ctrlAltCombo) {
if (Utils.isKeyPressed(e, KeyCodes.K)) { if (Keyboard.isKeyPressed(e, KeyCodes.K)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.applyMarkdown({ this.applyMarkdown({
@@ -1219,7 +1219,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
selectionEnd, selectionEnd,
message: value, message: value,
}); });
} else if (Utils.isKeyPressed(e, KeyCodes.C)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.C)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.applyMarkdown({ this.applyMarkdown({
@@ -1228,21 +1228,21 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
selectionEnd, selectionEnd,
message: value, message: value,
}); });
} else if (Utils.isKeyPressed(e, KeyCodes.E)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.E)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.toggleEmojiPicker(); this.toggleEmojiPicker();
} else if (Utils.isKeyPressed(e, KeyCodes.T)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.T)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.toggleAdvanceTextEditor(); this.toggleAdvanceTextEditor();
} else if (Utils.isKeyPressed(e, KeyCodes.P) && message.length) { } else if (Keyboard.isKeyPressed(e, KeyCodes.P) && message.length) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.setShowPreview(!this.props.shouldShowPreview); this.setShowPreview(!this.props.shouldShowPreview);
} }
} else if (shiftAltCombo) { } else if (shiftAltCombo) {
if (Utils.isKeyPressed(e, KeyCodes.X)) { if (Keyboard.isKeyPressed(e, KeyCodes.X)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.applyMarkdown({ this.applyMarkdown({
@@ -1251,7 +1251,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
selectionEnd, selectionEnd,
message: value, message: value,
}); });
} else if (Utils.isKeyPressed(e, KeyCodes.SEVEN)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.SEVEN)) {
e.preventDefault(); e.preventDefault();
this.applyMarkdown({ this.applyMarkdown({
markdownMode: 'ol', markdownMode: 'ol',
@@ -1259,7 +1259,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
selectionEnd, selectionEnd,
message: value, message: value,
}); });
} else if (Utils.isKeyPressed(e, KeyCodes.EIGHT)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.EIGHT)) {
e.preventDefault(); e.preventDefault();
this.applyMarkdown({ this.applyMarkdown({
markdownMode: 'ul', markdownMode: 'ul',
@@ -1267,7 +1267,7 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
selectionEnd, selectionEnd,
message: value, message: value,
}); });
} else if (Utils.isKeyPressed(e, KeyCodes.NINE)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.NINE)) {
e.preventDefault(); e.preventDefault();
this.applyMarkdown({ this.applyMarkdown({
markdownMode: 'quote', markdownMode: 'quote',
@@ -1277,21 +1277,21 @@ class AdvancedCreatePost extends React.PureComponent<Props, State> {
}); });
} }
} }
const upKeyOnly = !ctrlOrMetaKeyPressed && !e.altKey && !e.shiftKey && Utils.isKeyPressed(e, KeyCodes.UP); const upKeyOnly = !ctrlOrMetaKeyPressed && !e.altKey && !e.shiftKey && Keyboard.isKeyPressed(e, KeyCodes.UP);
const shiftUpKeyCombo = !ctrlOrMetaKeyPressed && !e.altKey && e.shiftKey && Utils.isKeyPressed(e, KeyCodes.UP); const shiftUpKeyCombo = !ctrlOrMetaKeyPressed && !e.altKey && e.shiftKey && Keyboard.isKeyPressed(e, KeyCodes.UP);
const ctrlShiftCombo = Utils.cmdOrCtrlPressed(e, true) && e.shiftKey; const ctrlShiftCombo = Keyboard.cmdOrCtrlPressed(e, true) && e.shiftKey;
if (upKeyOnly && messageIsEmpty) { if (upKeyOnly && messageIsEmpty) {
this.editLastPost(e); this.editLastPost(e);
} else if (shiftUpKeyCombo && messageIsEmpty) { } else if (shiftUpKeyCombo && messageIsEmpty) {
this.replyToLastPost(e); this.replyToLastPost(e);
} else if (ctrlShiftCombo && Utils.isKeyPressed(e, KeyCodes.E)) { } else if (ctrlShiftCombo && Keyboard.isKeyPressed(e, KeyCodes.E)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
this.toggleEmojiPicker(); 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); this.setShowPreview(!this.props.shouldShowPreview);
} else if (ctrlAltCombo && Utils.isKeyPressed(e, KeyCodes.T)) { } else if (ctrlAltCombo && Keyboard.isKeyPressed(e, KeyCodes.T)) {
this.toggleAdvanceTextEditor(); this.toggleAdvanceTextEditor();
} }
}; };

Просмотреть файл

@@ -12,9 +12,10 @@ import {Group} from '@mattermost/types/groups';
import ProfilePopover from 'components/profile_popover'; import ProfilePopover from 'components/profile_popover';
import {popOverOverlayPosition} from 'utils/position_utils'; import {popOverOverlayPosition} from 'utils/position_utils';
import {isKeyPressed} from 'utils/keyboard';
import {getUserOrGroupFromMentionName} from 'utils/post_utils'; import {getUserOrGroupFromMentionName} from 'utils/post_utils';
import Constants from 'utils/constants'; 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'; import AtMentionGroup from 'components/at_mention/at_mention_group';

Просмотреть файл

@@ -12,8 +12,9 @@ import ProfilePopover from 'components/profile_popover';
import UserGroupPopover from 'components/user_group_popover'; import UserGroupPopover from 'components/user_group_popover';
import Constants, {A11yCustomEventTypes, A11yFocusEventDetail} from 'utils/constants'; import Constants, {A11yCustomEventTypes, A11yFocusEventDetail} from 'utils/constants';
import {isKeyPressed} from 'utils/keyboard';
import {popOverOverlayPosition} from 'utils/position_utils'; 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'; import {MAX_LIST_HEIGHT, getListHeight, VIEWPORT_SCALE_FACTOR} from 'components/user_group_popover/group_member_list/group_member_list';

Просмотреть файл

@@ -6,7 +6,7 @@ import {FormattedMessage} from 'react-intl';
import styled from 'styled-components'; import styled from 'styled-components';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import {isKeyPressed} from 'utils/utils'; import {isKeyPressed} from 'utils/keyboard';
const Title = styled.div` const Title = styled.div`
flex:1; flex:1;

Просмотреть файл

@@ -25,7 +25,8 @@ import {GlobalState} from 'types/store';
import {getCurrentMomentForTimezone} from 'utils/timezone'; import {getCurrentMomentForTimezone} from 'utils/timezone';
import {A11yCustomEventTypes, A11yFocusEventDetail, Constants, ModalIdentifiers} from 'utils/constants'; import {A11yCustomEventTypes, A11yFocusEventDetail, Constants, ModalIdentifiers} from 'utils/constants';
import {t} from 'utils/i18n'; 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 CustomStatusSuggestion from 'components/custom_status/custom_status_suggestion';
import ExpiryMenu from 'components/custom_status/expiry_menu'; import ExpiryMenu from 'components/custom_status/expiry_menu';

Просмотреть файл

@@ -17,7 +17,8 @@ import DatePicker from 'components/date_picker';
import Menu from 'components/widgets/menu/menu'; import Menu from 'components/widgets/menu/menu';
import Timestamp from 'components/timestamp'; import Timestamp from 'components/timestamp';
import {getCurrentLocale} from 'selectors/i18n'; 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 {getCurrentMomentForTimezone} from 'utils/timezone';
import Constants, {A11yCustomEventTypes, A11yFocusEventDetail} from 'utils/constants'; import Constants, {A11yCustomEventTypes, A11yFocusEventDetail} from 'utils/constants';

Просмотреть файл

@@ -22,7 +22,8 @@ import MenuWrapper from 'components/widgets/menu/menu_wrapper';
import './dnd_custom_time_picker_modal.scss'; import './dnd_custom_time_picker_modal.scss';
import {toUTCUnix} from 'utils/datetime'; 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 Input from 'components/widgets/inputs/input/input';
import DatePicker from 'components/date_picker'; import DatePicker from 'components/date_picker';

Просмотреть файл

@@ -28,6 +28,7 @@ import Permissions from 'mattermost-redux/constants/permissions';
import {Locations, ModalIdentifiers, Constants, TELEMETRY_LABELS} from 'utils/constants'; import {Locations, ModalIdentifiers, Constants, TELEMETRY_LABELS} from 'utils/constants';
import DeletePostModal from 'components/delete_post_modal'; import DeletePostModal from 'components/delete_post_modal';
import DelayedAction from 'utils/delayed_action'; import DelayedAction from 'utils/delayed_action';
import * as Keyboard from 'utils/keyboard';
import * as PostUtils from 'utils/post_utils'; import * as PostUtils from 'utils/post_utils';
import * as Menu from 'components/menu'; import * as Menu from 'components/menu';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
@@ -337,61 +338,61 @@ export class DotMenuClass extends React.PureComponent<Props, State> {
const isShiftKeyPressed = e.shiftKey; const isShiftKeyPressed = e.shiftKey;
switch (true) { switch (true) {
case Utils.isKeyPressed(e, Constants.KeyCodes.R): case Keyboard.isKeyPressed(e, Constants.KeyCodes.R):
this.handleCommentClick(e); this.handleCommentClick(e);
this.handleDropdownOpened(false); this.handleDropdownOpened(false);
break; break;
// edit post // edit post
case Utils.isKeyPressed(e, Constants.KeyCodes.E): case Keyboard.isKeyPressed(e, Constants.KeyCodes.E):
this.handleEditMenuItemActivated(e); this.handleEditMenuItemActivated(e);
this.handleDropdownOpened(false); this.handleDropdownOpened(false);
break; break;
// follow thread // follow thread
case Utils.isKeyPressed(e, Constants.KeyCodes.F) && !isShiftKeyPressed: case Keyboard.isKeyPressed(e, Constants.KeyCodes.F) && !isShiftKeyPressed:
this.handleSetThreadFollow(e); this.handleSetThreadFollow(e);
this.handleDropdownOpened(false); this.handleDropdownOpened(false);
break; break;
// forward post // forward post
case Utils.isKeyPressed(e, Constants.KeyCodes.F) && isShiftKeyPressed: case Keyboard.isKeyPressed(e, Constants.KeyCodes.F) && isShiftKeyPressed:
this.handleForwardMenuItemActivated(e); this.handleForwardMenuItemActivated(e);
this.handleDropdownOpened(false); this.handleDropdownOpened(false);
break; break;
// copy link // copy link
case Utils.isKeyPressed(e, Constants.KeyCodes.K): case Keyboard.isKeyPressed(e, Constants.KeyCodes.K):
this.copyLink(e); this.copyLink(e);
this.handleDropdownOpened(false); this.handleDropdownOpened(false);
break; break;
// copy text // copy text
case Utils.isKeyPressed(e, Constants.KeyCodes.C): case Keyboard.isKeyPressed(e, Constants.KeyCodes.C):
this.copyText(e); this.copyText(e);
this.handleDropdownOpened(false); this.handleDropdownOpened(false);
break; break;
// delete post // delete post
case Utils.isKeyPressed(e, Constants.KeyCodes.DELETE): case Keyboard.isKeyPressed(e, Constants.KeyCodes.DELETE):
this.handleDeleteMenuItemActivated(e); this.handleDeleteMenuItemActivated(e);
this.handleDropdownOpened(false); this.handleDropdownOpened(false);
break; break;
// pin / unpin // pin / unpin
case Utils.isKeyPressed(e, Constants.KeyCodes.P): case Keyboard.isKeyPressed(e, Constants.KeyCodes.P):
this.handlePinMenuItemActivated(e); this.handlePinMenuItemActivated(e);
this.handleDropdownOpened(false); this.handleDropdownOpened(false);
break; break;
// save / unsave // save / unsave
case Utils.isKeyPressed(e, Constants.KeyCodes.S): case Keyboard.isKeyPressed(e, Constants.KeyCodes.S):
this.handleFlagMenuItemActivated(e); this.handleFlagMenuItemActivated(e);
this.handleDropdownOpened(false); this.handleDropdownOpened(false);
break; break;
// mark as unread // mark as unread
case Utils.isKeyPressed(e, Constants.KeyCodes.U): case Keyboard.isKeyPressed(e, Constants.KeyCodes.U):
this.handleMarkPostAsUnread(e); this.handleMarkPostAsUnread(e);
this.handleDropdownOpened(false); this.handleDropdownOpened(false);
break; break;

Просмотреть файл

@@ -6,7 +6,7 @@ import {connect} from 'react-redux';
import {makeGetChannel} from 'mattermost-redux/selectors/entities/channels'; import {makeGetChannel} from 'mattermost-redux/selectors/entities/channels';
import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams'; import {getCurrentTeamId} from 'mattermost-redux/selectors/entities/teams';
import {getChannelURL} from 'utils/utils'; import {getChannelURL} from 'selectors/urls';
import {GlobalState} from 'types/store'; import {GlobalState} from 'types/store';

Просмотреть файл

@@ -13,8 +13,9 @@ import Textbox, {TextboxElement} from 'components/textbox';
import TextboxClass from 'components/textbox/textbox'; import TextboxClass from 'components/textbox/textbox';
import TextboxLinks from 'components/textbox/textbox_links'; import TextboxLinks from 'components/textbox/textbox_links';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import {isKeyPressed} from 'utils/keyboard';
import {isMobile} from 'utils/user_agent'; 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; const KeyCodes = Constants.KeyCodes;

Просмотреть файл

@@ -9,6 +9,7 @@ import {Channel} from '@mattermost/types/channels';
import {ActionResult} from 'mattermost-redux/types/actions'; import {ActionResult} from 'mattermost-redux/types/actions';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import * as Keyboard from 'utils/keyboard';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
type Actions = { type Actions = {
@@ -68,10 +69,10 @@ export class EditChannelPurposeModal extends React.PureComponent<Props, State> {
if (Utils.isUnhandledLineBreakKeyCombo(e)) { if (Utils.isUnhandledLineBreakKeyCombo(e)) {
e.preventDefault(); e.preventDefault();
this.setState({purpose: Utils.insertLineBreakFromKeyEvent(e as React.KeyboardEvent<HTMLTextAreaElement>)}); this.setState({purpose: Utils.insertLineBreakFromKeyEvent(e as React.KeyboardEvent<HTMLTextAreaElement>)});
} else if (ctrlSend && Utils.isKeyPressed(e, Constants.KeyCodes.ENTER) && e.ctrlKey) { } else if (ctrlSend && Keyboard.isKeyPressed(e, Constants.KeyCodes.ENTER) && e.ctrlKey) {
e.preventDefault(); e.preventDefault();
this.handleSave(); 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(); e.preventDefault();
this.handleSave(); this.handleSave();
} }

Просмотреть файл

@@ -10,6 +10,7 @@ import {Post} from '@mattermost/types/posts';
import {Emoji, SystemEmoji} from '@mattermost/types/emojis'; import {Emoji, SystemEmoji} from '@mattermost/types/emojis';
import {AppEvents, Constants, ModalIdentifiers, StoragePrefixes} from 'utils/constants'; import {AppEvents, Constants, ModalIdentifiers, StoragePrefixes} from 'utils/constants';
import * as Keyboard from 'utils/keyboard';
import { import {
formatGithubCodePaste, formatGithubCodePaste,
formatMarkdownMessage, formatMarkdownMessage,
@@ -308,13 +309,13 @@ const EditPost = ({editingPost, actions, canEditPost, config, channelId, draft,
const {ctrlSend, codeBlockOnCtrlEnter} = rest; const {ctrlSend, codeBlockOnCtrlEnter} = rest;
const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey; const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey;
const ctrlKeyCombo = Utils.cmdOrCtrlPressed(e) && !e.altKey && !e.shiftKey; const ctrlKeyCombo = Keyboard.cmdOrCtrlPressed(e) && !e.altKey && !e.shiftKey;
const ctrlAltCombo = Utils.cmdOrCtrlPressed(e, true) && e.altKey; const ctrlAltCombo = Keyboard.cmdOrCtrlPressed(e, true) && e.altKey;
const ctrlEnterKeyCombo = const ctrlEnterKeyCombo =
(ctrlSend || codeBlockOnCtrlEnter) && (ctrlSend || codeBlockOnCtrlEnter) &&
Utils.isKeyPressed(e, KeyCodes.ENTER) && Keyboard.isKeyPressed(e, KeyCodes.ENTER) &&
ctrlOrMetaKeyPressed; 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 // listen for line break key combo and insert new line character
if (Utils.isUnhandledLineBreakKeyCombo(e)) { if (Utils.isUnhandledLineBreakKeyCombo(e)) {
@@ -322,7 +323,7 @@ const EditPost = ({editingPost, actions, canEditPost, config, channelId, draft,
setEditText(Utils.insertLineBreakFromKeyEvent(e as React.KeyboardEvent<HTMLTextAreaElement>)); setEditText(Utils.insertLineBreakFromKeyEvent(e as React.KeyboardEvent<HTMLTextAreaElement>));
} else if (ctrlEnterKeyCombo) { } else if (ctrlEnterKeyCombo) {
handleEdit(); handleEdit();
} else if (Utils.isKeyPressed(e, KeyCodes.ESCAPE) && !showEmojiPicker) { } else if (Keyboard.isKeyPressed(e, KeyCodes.ESCAPE) && !showEmojiPicker) {
handleAutomatedRefocusAndExit(); handleAutomatedRefocusAndExit();
} else if (ctrlAltCombo && markdownLinkKey) { } else if (ctrlAltCombo && markdownLinkKey) {
applyHotkeyMarkdown({ applyHotkeyMarkdown({
@@ -331,14 +332,14 @@ const EditPost = ({editingPost, actions, canEditPost, config, channelId, draft,
selectionEnd: e.currentTarget.selectionEnd, selectionEnd: e.currentTarget.selectionEnd,
message: e.currentTarget.value, message: e.currentTarget.value,
}); });
} else if (ctrlKeyCombo && Utils.isKeyPressed(e, KeyCodes.B)) { } else if (ctrlKeyCombo && Keyboard.isKeyPressed(e, KeyCodes.B)) {
applyHotkeyMarkdown({ applyHotkeyMarkdown({
markdownMode: 'bold', markdownMode: 'bold',
selectionStart: e.currentTarget.selectionStart, selectionStart: e.currentTarget.selectionStart,
selectionEnd: e.currentTarget.selectionEnd, selectionEnd: e.currentTarget.selectionEnd,
message: e.currentTarget.value, message: e.currentTarget.value,
}); });
} else if (ctrlKeyCombo && Utils.isKeyPressed(e, KeyCodes.I)) { } else if (ctrlKeyCombo && Keyboard.isKeyPressed(e, KeyCodes.I)) {
applyHotkeyMarkdown({ applyHotkeyMarkdown({
markdownMode: 'italic', markdownMode: 'italic',
selectionStart: e.currentTarget.selectionStart, selectionStart: e.currentTarget.selectionStart,

Просмотреть файл

@@ -8,7 +8,7 @@ import {FormattedMessage} from 'react-intl';
import {getBool} from 'mattermost-redux/selectors/entities/preferences'; import {getBool} from 'mattermost-redux/selectors/entities/preferences';
import {Preferences} from 'mattermost-redux/constants'; import {Preferences} from 'mattermost-redux/constants';
import {isMac} from 'utils/utils'; import {isMac} from 'utils/user_agent';
import {GlobalState} from 'types/store'; import {GlobalState} from 'types/store';
type Props = { type Props = {

Просмотреть файл

@@ -12,6 +12,7 @@ import {Post} from '@mattermost/types/posts';
import {getFileDownloadUrl, getFilePreviewUrl, getFileUrl} from 'mattermost-redux/utils/file_utils'; import {getFileDownloadUrl, getFilePreviewUrl, getFileUrl} from 'mattermost-redux/utils/file_utils';
import LoadingImagePreview from 'components/loading_image_preview'; import LoadingImagePreview from 'components/loading_image_preview';
import Constants, {FileTypes, ZoomSettings} from 'utils/constants'; import Constants, {FileTypes, ZoomSettings} from 'utils/constants';
import * as Keyboard from 'utils/keyboard';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
import AudioVideoPreview from 'components/audio_video_preview'; import AudioVideoPreview from 'components/audio_video_preview';
import CodePreview from 'components/code_preview'; import CodePreview from 'components/code_preview';
@@ -115,9 +116,9 @@ export default class FilePreviewModal extends React.PureComponent<Props, State>
}; };
handleKeyPress = (e: KeyboardEvent) => { handleKeyPress = (e: KeyboardEvent) => {
if (Utils.isKeyPressed(e, KeyCodes.RIGHT)) { if (Keyboard.isKeyPressed(e, KeyCodes.RIGHT)) {
this.handleNext(); this.handleNext();
} else if (Utils.isKeyPressed(e, KeyCodes.LEFT)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.LEFT)) {
this.handlePrev(); this.handlePrev();
} }
}; };

Просмотреть файл

@@ -12,6 +12,7 @@ import dragster from 'utils/dragster';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import DelayedAction from 'utils/delayed_action'; import DelayedAction from 'utils/delayed_action';
import {t} from 'utils/i18n'; import {t} from 'utils/i18n';
import {cmdOrCtrlPressed, isKeyPressed} from 'utils/keyboard';
import { import {
isIosChrome, isIosChrome,
isMobileApp, isMobileApp,
@@ -19,8 +20,6 @@ import {
import {getTable} from 'utils/paste'; import {getTable} from 'utils/paste';
import { import {
clearFileInput, clearFileInput,
cmdOrCtrlPressed,
isKeyPressed,
generateId, generateId,
isFileTransfer, isFileTransfer,
isUriDrop, isUriDrop,

Просмотреть файл

@@ -12,6 +12,7 @@ import Textbox, {TextboxClass, TextboxElement} from 'components/textbox';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import {applyMarkdown, ApplyMarkdownOptions} from 'utils/markdown/apply_markdown'; import {applyMarkdown, ApplyMarkdownOptions} from 'utils/markdown/apply_markdown';
import * as Keyboard from 'utils/keyboard';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
import {GlobalState} from 'types/store'; import {GlobalState} from 'types/store';
@@ -77,13 +78,13 @@ const ForwardPostCommentInput = ({channelId, canForwardPost, comment, permaLinkL
}; };
const handleKeyDown = (e: React.KeyboardEvent<TextboxElement>) => { const handleKeyDown = (e: React.KeyboardEvent<TextboxElement>) => {
const ctrlKeyCombo = Utils.cmdOrCtrlPressed(e) && !e.altKey && !e.shiftKey; const ctrlKeyCombo = Keyboard.cmdOrCtrlPressed(e) && !e.altKey && !e.shiftKey;
const ctrlAltCombo = Utils.cmdOrCtrlPressed(e, true) && e.altKey; const ctrlAltCombo = Keyboard.cmdOrCtrlPressed(e, true) && e.altKey;
const ctrlShiftCombo = Utils.cmdOrCtrlPressed(e, true) && e.shiftKey; const ctrlShiftCombo = Keyboard.cmdOrCtrlPressed(e, true) && e.shiftKey;
const markdownLinkKey = Utils.isKeyPressed(e, KeyCodes.K); const markdownLinkKey = Keyboard.isKeyPressed(e, KeyCodes.K);
const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey; const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey;
const ctrlEnterKeyCombo = const ctrlEnterKeyCombo =
Utils.isKeyPressed(e, KeyCodes.ENTER) && ctrlOrMetaKeyPressed; Keyboard.isKeyPressed(e, KeyCodes.ENTER) && ctrlOrMetaKeyPressed;
const {selectionStart, selectionEnd, value} = const {selectionStart, selectionEnd, value} =
e.target as TextboxElement; e.target as TextboxElement;
@@ -98,28 +99,28 @@ const ForwardPostCommentInput = ({channelId, canForwardPost, comment, permaLinkL
selectionEnd, selectionEnd,
message: value, message: value,
}); });
} else if (ctrlKeyCombo && Utils.isKeyPressed(e, KeyCodes.B)) { } else if (ctrlKeyCombo && Keyboard.isKeyPressed(e, KeyCodes.B)) {
applyMarkdownMode({ applyMarkdownMode({
markdownMode: 'bold', markdownMode: 'bold',
selectionStart, selectionStart,
selectionEnd, selectionEnd,
message: value, message: value,
}); });
} else if (ctrlKeyCombo && Utils.isKeyPressed(e, KeyCodes.I)) { } else if (ctrlKeyCombo && Keyboard.isKeyPressed(e, KeyCodes.I)) {
applyMarkdownMode({ applyMarkdownMode({
markdownMode: 'italic', markdownMode: 'italic',
selectionStart, selectionStart,
selectionEnd, selectionEnd,
message: value, message: value,
}); });
} else if (ctrlShiftCombo && Utils.isKeyPressed(e, KeyCodes.X)) { } else if (ctrlShiftCombo && Keyboard.isKeyPressed(e, KeyCodes.X)) {
applyMarkdownMode({ applyMarkdownMode({
markdownMode: 'strike', markdownMode: 'strike',
selectionStart, selectionStart,
selectionEnd, selectionEnd,
message: value, message: value,
}); });
} else if (ctrlShiftCombo && Utils.isKeyPressed(e, KeyCodes.E)) { } else if (ctrlShiftCombo && Keyboard.isKeyPressed(e, KeyCodes.E)) {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
} else if (ctrlEnterKeyCombo && canForwardPost) { } else if (ctrlEnterKeyCombo && canForwardPost) {

Просмотреть файл

@@ -16,6 +16,8 @@ import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
import NotificationBox from 'components/notification_box'; import NotificationBox from 'components/notification_box';
import {getPermalinkURL} from 'selectors/urls';
import {GlobalState} from 'types/store'; import {GlobalState} from 'types/store';
import {haveIChannelPermission} from 'mattermost-redux/selectors/entities/roles'; 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 {PostPreviewMetadata} from '@mattermost/types/posts';
import {getSiteURL} from '../../utils/url'; import {getSiteURL} from '../../utils/url';
import * as Utils from '../../utils/utils';
import ForwardPostChannelSelect, {ChannelOption, makeSelectedChannelOption} from './forward_post_channel_select'; import ForwardPostChannelSelect, {ChannelOption, makeSelectedChannelOption} from './forward_post_channel_select';
import ForwardPostCommentInput from './forward_post_comment_input'; 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 channel = useSelector((state: GlobalState) => getChannel(state, {id: post.channel_id}));
const currentTeam = useSelector(getCurrentTeam); 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 permaLink = `${getSiteURL()}${relativePermaLink}`;
const isPrivateConversation = channel.type !== Constants.OPEN_CHANNEL; const isPrivateConversation = channel.type !== Constants.OPEN_CHANNEL;

Просмотреть файл

@@ -17,7 +17,7 @@ import {
Constants, Constants,
RHSStates, RHSStates,
} from 'utils/constants'; } from 'utils/constants';
import * as Utils from 'utils/utils'; import * as Keyboard from 'utils/keyboard';
const GlobalSearchNav = (): JSX.Element => { const GlobalSearchNav = (): JSX.Element => {
const dispatch = useDispatch(); const dispatch = useDispatch();
@@ -25,8 +25,8 @@ const GlobalSearchNav = (): JSX.Element => {
useEffect(() => { useEffect(() => {
const handleShortcut = (e: KeyboardEvent) => { const handleShortcut = (e: KeyboardEvent) => {
if (Utils.cmdOrCtrlPressed(e) && e.shiftKey) { if (Keyboard.cmdOrCtrlPressed(e) && e.shiftKey) {
if (Utils.isKeyPressed(e, Constants.KeyCodes.M)) { if (Keyboard.isKeyPressed(e, Constants.KeyCodes.M)) {
e.preventDefault(); e.preventDefault();
if (rhsState === RHSStates.MENTION) { if (rhsState === RHSStates.MENTION) {
dispatch(closeRightHandSide()); dispatch(closeRightHandSide());

Просмотреть файл

@@ -11,7 +11,7 @@ import {GlobalState} from 'types/store';
import {suitePluginIds} from 'utils/constants'; import {suitePluginIds} from 'utils/constants';
import {t} from 'utils/i18n'; import {t} from 'utils/i18n';
import * as Utils from 'utils/utils'; import * as UserAgent from 'utils/user_agent';
import KeyboardShortcutSequence, { import KeyboardShortcutSequence, {
KEYBOARD_SHORTCUTS, KEYBOARD_SHORTCUTS,
@@ -91,7 +91,7 @@ const KeyboardShortcutsModal = ({onExited}: Props): JSX.Element => {
const handleHide = useCallback(() => setShow(false), []); const handleHide = useCallback(() => setShow(false), []);
const isLinux = Utils.isLinux(); const isLinux = UserAgent.isLinux();
const isCallsEnabled = useSelector((state: GlobalState) => { const isCallsEnabled = useSelector((state: GlobalState) => {
return Boolean(state.plugins.plugins[suitePluginIds.calls]); return Boolean(state.plugins.plugins[suitePluginIds.calls]);

Просмотреть файл

@@ -6,7 +6,7 @@ import React, {memo} from 'react';
import {useIntl} from 'react-intl'; import {useIntl} from 'react-intl';
import {ShortcutKeyVariant, ShortcutKey} from 'components/shortcut_key'; import {ShortcutKeyVariant, ShortcutKey} from 'components/shortcut_key';
import {isMac} from 'utils/utils'; import {isMac} from 'utils/user_agent';
import {isMessageDescriptor, KeyboardShortcutDescriptor} from './keyboard_shortcuts'; import {isMessageDescriptor, KeyboardShortcutDescriptor} from './keyboard_shortcuts';

Просмотреть файл

@@ -12,8 +12,7 @@ import * as UserUtils from 'mattermost-redux/utils/user_utils';
import FormattedMarkdownMessage from 'components/formatted_markdown_message'; import FormattedMarkdownMessage from 'components/formatted_markdown_message';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import {isKeyPressed} from 'utils/keyboard';
import {isKeyPressed} from 'utils/utils';
type Props = { type Props = {
currentUser: UserProfile; currentUser: UserProfile;

Просмотреть файл

@@ -16,9 +16,10 @@ import {getCurrentChannelId} from 'mattermost-redux/selectors/entities/channels'
import {getLicense, getConfig} from 'mattermost-redux/selectors/entities/general'; import {getLicense, getConfig} from 'mattermost-redux/selectors/entities/general';
import {getCurrentUser, shouldShowTermsOfService} from 'mattermost-redux/selectors/entities/users'; import {getCurrentUser, shouldShowTermsOfService} from 'mattermost-redux/selectors/entities/users';
import {getChannelURL} from 'selectors/urls';
import {getHistory} from 'utils/browser_history'; import {getHistory} from 'utils/browser_history';
import {checkIfMFARequired} from 'utils/route'; import {checkIfMFARequired} from 'utils/route';
import {getChannelURL} from 'utils/utils';
import {isPermalinkURL} from 'utils/url'; import {isPermalinkURL} from 'utils/url';
import LoggedIn from './logged_in'; import LoggedIn from './logged_in';

Просмотреть файл

@@ -9,7 +9,7 @@ import {Permissions} from 'mattermost-redux/constants';
import * as GlobalActions from 'actions/global_actions'; import * as GlobalActions from 'actions/global_actions';
import {FREEMIUM_TO_ENTERPRISE_TRIAL_LENGTH_DAYS} from 'utils/cloud_utils'; import {FREEMIUM_TO_ENTERPRISE_TRIAL_LENGTH_DAYS} from 'utils/cloud_utils';
import {Constants, LicenseSkus, ModalIdentifiers, MattermostFeatures} from 'utils/constants'; 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 {makeUrlSafe} from 'utils/url';
import * as UserAgent from 'utils/user_agent'; import * as UserAgent from 'utils/user_agent';
import InvitationModal from 'components/invitation_modal'; import InvitationModal from 'components/invitation_modal';

Просмотреть файл

@@ -20,7 +20,7 @@ import {getIsMobileView} from 'selectors/views/browser';
import {openModal, closeModal} from 'actions/views/modals'; import {openModal, closeModal} from 'actions/views/modals';
import Constants, {A11yClassNames} from 'utils/constants'; import Constants, {A11yClassNames} from 'utils/constants';
import {isKeyPressed} from 'utils/utils'; import {isKeyPressed} from 'utils/keyboard';
import CompassDesignProvider from 'components/compass_design_provider'; import CompassDesignProvider from 'components/compass_design_provider';
import Tooltip from 'components/tooltip'; import Tooltip from 'components/tooltip';

Просмотреть файл

@@ -7,7 +7,7 @@ import MuiMenuItem from '@mui/material/MenuItem';
import type {MenuItemProps as MuiMenuItemProps} from '@mui/material/MenuItem'; import type {MenuItemProps as MuiMenuItemProps} from '@mui/material/MenuItem';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import {isKeyPressed} from 'utils/utils'; import {isKeyPressed} from 'utils/keyboard';
export interface Props extends MuiMenuItemProps { export interface Props extends MuiMenuItemProps {

Просмотреть файл

@@ -14,7 +14,7 @@ import {isAnyModalOpen} from 'selectors/views/modals';
import {openModal, closeModal} from 'actions/views/modals'; import {openModal, closeModal} from 'actions/views/modals';
import Constants, {A11yClassNames} from 'utils/constants'; import Constants, {A11yClassNames} from 'utils/constants';
import {isKeyPressed} from 'utils/utils'; import {isKeyPressed} from 'utils/keyboard';
import CompassDesignProvider from 'components/compass_design_provider'; import CompassDesignProvider from 'components/compass_design_provider';
import GenericModal from 'components/generic_modal'; import GenericModal from 'components/generic_modal';

Просмотреть файл

@@ -5,7 +5,7 @@ import React from 'react';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import {isKeyPressed} from 'utils/utils'; import {isKeyPressed} from 'utils/keyboard';
import {redirectUserToDefaultTeam} from 'actions/global_actions'; import {redirectUserToDefaultTeam} from 'actions/global_actions';

Просмотреть файл

@@ -8,7 +8,7 @@ import {getOptionValue} from 'react-select/src/builtins';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import {cmdOrCtrlPressed} from 'utils/utils'; import {cmdOrCtrlPressed} from 'utils/keyboard';
import LoadingScreen from 'components/loading_screen'; import LoadingScreen from 'components/loading_screen';

Просмотреть файл

@@ -5,7 +5,7 @@ import React, {memo, useEffect, useCallback} from 'react';
import {FormattedMessage, useIntl} from 'react-intl'; import {FormattedMessage, useIntl} from 'react-intl';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import {isKeyPressed} from 'utils/utils'; import {isKeyPressed} from 'utils/keyboard';
import Toast from 'components/toast/toast'; import Toast from 'components/toast/toast';

Просмотреть файл

@@ -6,7 +6,8 @@ import {FormattedMessage} from 'react-intl';
import {Moment} from 'moment-timezone'; import {Moment} from 'moment-timezone';
import GenericModal from 'components/generic_modal'; 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 DateTimeInput, {getRoundedTime} from 'components/custom_status/date_time_input';
import {toUTCUnix} from 'utils/datetime'; import {toUTCUnix} from 'utils/datetime';

Просмотреть файл

@@ -20,6 +20,7 @@ import {ModalData} from 'types/actions';
import {getHistory} from 'utils/browser_history'; import {getHistory} from 'utils/browser_history';
import Constants, {A11yClassNames, A11yCustomEventTypes, A11yFocusEventDetail, ModalIdentifiers, UserStatuses} from 'utils/constants'; import Constants, {A11yClassNames, A11yCustomEventTypes, A11yFocusEventDetail, ModalIdentifiers, UserStatuses} from 'utils/constants';
import {t} from 'utils/i18n'; import {t} from 'utils/i18n';
import * as Keyboard from 'utils/keyboard';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
import {shouldFocusMainTextbox} from 'utils/post_utils'; import {shouldFocusMainTextbox} from 'utils/post_utils';
@@ -338,7 +339,7 @@ class ProfilePopover extends React.PureComponent<ProfilePopoverProps, ProfilePop
handleKeyDown = (e: React.KeyboardEvent) => { handleKeyDown = (e: React.KeyboardEvent) => {
if (shouldFocusMainTextbox(e, document.activeElement)) { if (shouldFocusMainTextbox(e, document.activeElement)) {
this.props.hide?.(); this.props.hide?.();
} else if (Utils.isKeyPressed(e, Constants.KeyCodes.ESCAPE)) { } else if (Keyboard.isKeyPressed(e, Constants.KeyCodes.ESCAPE)) {
this.returnFocus(); this.returnFocus();
} }
}; };

Просмотреть файл

@@ -11,7 +11,7 @@ import {getCurrentChannelNameForSearchShortcut} from 'mattermost-redux/selectors
import {isServerVersionGreaterThanOrEqualTo} from 'utils/server_version'; import {isServerVersionGreaterThanOrEqualTo} from 'utils/server_version';
import {isDesktopApp, getDesktopVersion, isMacApp} from 'utils/user_agent'; import {isDesktopApp, getDesktopVersion, isMacApp} from 'utils/user_agent';
import Constants, {searchHintOptions, RHSStates, searchFilesHintOptions} from 'utils/constants'; 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 HeaderIconWrapper from 'components/channel_header/components/header_icon_wrapper';
import SearchHint from 'components/search_hint/search_hint'; import SearchHint from 'components/search_hint/search_hint';
@@ -109,7 +109,7 @@ const Search: React.FC<Props> = (props: Props): JSX.Element => {
} }
const handleKeyDown = (e: KeyboardEvent) => { 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) { if (!isDesktop && !e.shiftKey) {
return; return;
} }

Просмотреть файл

@@ -6,7 +6,7 @@ import classNames from 'classnames';
import {FormattedMessage, useIntl} from 'react-intl'; import {FormattedMessage, useIntl} from 'react-intl';
import Constants from 'utils/constants'; 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 SuggestionDate from 'components/suggestion/suggestion_date';
import SearchSuggestionList from 'components/suggestion/search_suggestion_list'; import SearchSuggestionList from 'components/suggestion/search_suggestion_list';
@@ -71,27 +71,27 @@ const SearchBar: React.FunctionComponent<Props> = (props: Props): JSX.Element =>
}, [searchTerms]); }, [searchTerms]);
const handleKeyDown = (e: ChangeEvent<HTMLInputElement>): void => { const handleKeyDown = (e: ChangeEvent<HTMLInputElement>): void => {
if (Utils.isKeyPressed(e as any, KeyCodes.ESCAPE)) { if (Keyboard.isKeyPressed(e as any, KeyCodes.ESCAPE)) {
searchRef.current?.blur(); searchRef.current?.blur();
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
} }
if (Utils.isKeyPressed(e as any, KeyCodes.DOWN)) { if (Keyboard.isKeyPressed(e as any, KeyCodes.DOWN)) {
e.preventDefault(); e.preventDefault();
props.updateHighlightedSearchHint(1, true); props.updateHighlightedSearchHint(1, true);
} }
if (Utils.isKeyPressed(e as any, KeyCodes.UP)) { if (Keyboard.isKeyPressed(e as any, KeyCodes.UP)) {
e.preventDefault(); e.preventDefault();
props.updateHighlightedSearchHint(-1, true); props.updateHighlightedSearchHint(-1, true);
} }
if (Utils.isKeyPressed(e as any, KeyCodes.ENTER)) { if (Keyboard.isKeyPressed(e as any, KeyCodes.ENTER)) {
props.handleEnterKey(e); props.handleEnterKey(e);
} }
if (Utils.isKeyPressed(e as any, KeyCodes.BACKSPACE) && !searchTerms) { if (Keyboard.isKeyPressed(e as any, KeyCodes.BACKSPACE) && !searchTerms) {
if (props.clearSearchType) { if (props.clearSearchType) {
props.clearSearchType(); props.clearSearchType();
} }

Просмотреть файл

@@ -7,7 +7,7 @@ import {FormattedMessage} from 'react-intl';
import {SearchFilterType} from '../search/types'; import {SearchFilterType} from '../search/types';
import {SearchType} from 'types/store/rhs'; import {SearchType} from 'types/store/rhs';
import * as Utils from 'utils/utils'; import * as Keyboard from 'utils/keyboard';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import FilesFilterMenu from './files_filter_menu'; import FilesFilterMenu from './files_filter_menu';
@@ -32,7 +32,7 @@ export default function MessagesOrFilesSelector(props: Props): JSX.Element {
<div className='buttons-container'> <div className='buttons-container'>
<button <button
onClick={() => props.onChange('messages')} onClick={() => props.onChange('messages')}
onKeyDown={(e: React.KeyboardEvent<HTMLSpanElement>) => Utils.isKeyPressed(e, KeyCodes.ENTER) && props.onChange('messages')} onKeyDown={(e: React.KeyboardEvent<HTMLSpanElement>) => Keyboard.isKeyPressed(e, KeyCodes.ENTER) && props.onChange('messages')}
className={props.selected === 'messages' ? 'active tab messages-tab' : 'tab messages-tab'} className={props.selected === 'messages' ? 'active tab messages-tab' : 'tab messages-tab'}
> >
<FormattedMessage <FormattedMessage
@@ -44,7 +44,7 @@ export default function MessagesOrFilesSelector(props: Props): JSX.Element {
{props.isFileAttachmentsEnabled && {props.isFileAttachmentsEnabled &&
<button <button
onClick={() => props.onChange('files')} onClick={() => props.onChange('files')}
onKeyDown={(e: React.KeyboardEvent<HTMLSpanElement>) => Utils.isKeyPressed(e, KeyCodes.ENTER) && props.onChange('files')} onKeyDown={(e: React.KeyboardEvent<HTMLSpanElement>) => Keyboard.isKeyPressed(e, KeyCodes.ENTER) && props.onChange('files')}
className={props.selected === 'files' ? 'active tab files-tab' : 'tab files-tab'} className={props.selected === 'files' ? 'active tab files-tab' : 'tab files-tab'}
> >
<FormattedMessage <FormattedMessage

Просмотреть файл

@@ -13,12 +13,6 @@ describe('components/SearchShortcut', () => {
return { return {
...original, ...original,
isDesktopApp: jest.fn(() => false), isDesktopApp: jest.fn(() => false),
};
});
jest.mock('utils/utils', () => {
const original = jest.requireActual('utils/utils');
return {
...original,
isMac: jest.fn(() => false), isMac: jest.fn(() => false),
}; };
}); });
@@ -33,12 +27,6 @@ describe('components/SearchShortcut', () => {
return { return {
...original, ...original,
isDesktopApp: jest.fn(() => false), isDesktopApp: jest.fn(() => false),
};
});
jest.mock('utils/utils', () => {
const original = jest.requireActual('utils/utils');
return {
...original,
isMac: jest.fn(() => true), isMac: jest.fn(() => true),
}; };
}); });
@@ -53,13 +41,7 @@ describe('components/SearchShortcut', () => {
return { return {
...original, ...original,
isDesktopApp: jest.fn(() => true), isDesktopApp: jest.fn(() => true),
}; isMac: jest.fn(() => true),
});
jest.mock('utils/utils', () => {
const original = jest.requireActual('utils/utils');
return {
...original,
isMac: jest.fn(() => false),
}; };
}); });
@@ -73,12 +55,6 @@ describe('components/SearchShortcut', () => {
return { return {
...original, ...original,
isDesktopApp: jest.fn(() => true), isDesktopApp: jest.fn(() => true),
};
});
jest.mock('utils/utils', () => {
const original = jest.requireActual('utils/utils');
return {
...original,
isMac: jest.fn(() => true), isMac: jest.fn(() => true),
}; };
}); });

Просмотреть файл

@@ -6,8 +6,7 @@ import classNames from 'classnames';
import {ShortcutKey, ShortcutKeyVariant} from 'components/shortcut_key'; import {ShortcutKey, ShortcutKeyVariant} from 'components/shortcut_key';
import {isMac} from 'utils/utils'; import {isDesktopApp, isMac} from 'utils/user_agent';
import {isDesktopApp} from 'utils/user_agent';
import './search_shortcut.scss'; import './search_shortcut.scss';

Просмотреть файл

@@ -6,7 +6,8 @@ import {FormattedMessage} from 'react-intl';
import SaveButton from 'components/save_button'; import SaveButton from 'components/save_button';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import {a11yFocus, isKeyPressed} from 'utils/utils'; import {isKeyPressed} from 'utils/keyboard';
import {a11yFocus} from 'utils/utils';
type Props = { type Props = {
// Array of inputs selection // Array of inputs selection

Просмотреть файл

@@ -5,7 +5,8 @@ import React, {RefObject} from 'react';
import * as UserAgent from 'utils/user_agent'; import * as UserAgent from 'utils/user_agent';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import {a11yFocus, isKeyPressed} from 'utils/utils'; import {isKeyPressed} from 'utils/keyboard';
import {a11yFocus} from 'utils/utils';
export type Tab = { export type Tab = {
icon: string; icon: string;

Просмотреть файл

@@ -9,7 +9,7 @@ import {injectIntl, IntlShape} from 'react-intl';
import {trackEvent} from 'actions/telemetry_actions'; import {trackEvent} from 'actions/telemetry_actions';
import OverlayTrigger from 'components/overlay_trigger'; import OverlayTrigger from 'components/overlay_trigger';
import Tooltip from 'components/tooltip'; import Tooltip from 'components/tooltip';
import * as Utils from 'utils/utils'; import * as Keyboard from 'utils/keyboard';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import KeyboardShortcutSequence, {KEYBOARD_SHORTCUTS} from 'components/keyboard_shortcuts/keyboard_shortcuts_sequence'; import KeyboardShortcutSequence, {KEYBOARD_SHORTCUTS} from 'components/keyboard_shortcuts/keyboard_shortcuts_sequence';
@@ -38,7 +38,7 @@ export class ChannelFilter extends React.PureComponent<Props> {
}; };
handleUnreadFilterKeyPress = (e: KeyboardEvent) => { handleUnreadFilterKeyPress = (e: KeyboardEvent) => {
if (Utils.cmdOrCtrlPressed(e) && e.shiftKey && Utils.isKeyPressed(e, Constants.KeyCodes.U)) { if (Keyboard.cmdOrCtrlPressed(e) && e.shiftKey && Keyboard.isKeyPressed(e, Constants.KeyCodes.U)) {
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
this.toggleUnreadFilter(); this.toggleUnreadFilter();

Просмотреть файл

@@ -11,6 +11,8 @@ import QuickSwitchModal from 'components/quick_switch_modal';
import {ModalData} from 'types/actions'; import {ModalData} from 'types/actions';
import Constants, {ModalIdentifiers} from 'utils/constants'; import Constants, {ModalIdentifiers} from 'utils/constants';
import * as Keyboard from 'utils/keyboard';
import * as UserAgent from 'utils/user_agent';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
import ChannelFilter from '../channel_filter'; import ChannelFilter from '../channel_filter';
@@ -64,12 +66,12 @@ export default class ChannelNavigator extends React.PureComponent<Props> {
handleShortcut = (e: KeyboardEvent) => { handleShortcut = (e: KeyboardEvent) => {
const {actions: {closeModal}} = this.props; const {actions: {closeModal}} = this.props;
if (Utils.cmdOrCtrlPressed(e) && e.shiftKey) { if (Keyboard.cmdOrCtrlPressed(e) && e.shiftKey) {
if (Utils.isKeyPressed(e, Constants.KeyCodes.M)) { if (Keyboard.isKeyPressed(e, Constants.KeyCodes.M)) {
e.preventDefault(); e.preventDefault();
closeModal(ModalIdentifiers.QUICK_SWITCH); closeModal(ModalIdentifiers.QUICK_SWITCH);
} }
if (Utils.isKeyPressed(e, Constants.KeyCodes.L)) { if (Keyboard.isKeyPressed(e, Constants.KeyCodes.L)) {
// just close the modal if it's open, but let someone else handle the shortcut // just close the modal if it's open, but let someone else handle the shortcut
closeModal(ModalIdentifiers.QUICK_SWITCH); closeModal(ModalIdentifiers.QUICK_SWITCH);
} }
@@ -77,7 +79,7 @@ export default class ChannelNavigator extends React.PureComponent<Props> {
}; };
handleQuickSwitchKeyPress = (e: KeyboardEvent) => { handleQuickSwitchKeyPress = (e: KeyboardEvent) => {
if (Utils.cmdOrCtrlPressed(e) && !e.shiftKey && Utils.isKeyPressed(e, Constants.KeyCodes.K)) { if (Keyboard.cmdOrCtrlPressed(e) && !e.shiftKey && Keyboard.isKeyPressed(e, Constants.KeyCodes.K)) {
if (!e.altKey) { if (!e.altKey) {
e.preventDefault(); e.preventDefault();
this.toggleQuickSwitchModal(); this.toggleQuickSwitchModal();
@@ -123,7 +125,7 @@ export default class ChannelNavigator extends React.PureComponent<Props> {
defaultMessage='Find channel' defaultMessage='Find channel'
/> />
<div className={'SidebarChannelNavigator_shortcutText'}> <div className={'SidebarChannelNavigator_shortcutText'}>
{`${Utils.isMac() ? '⌘' : 'Ctrl+'}K`} {`${UserAgent.isMac() ? '⌘' : 'Ctrl+'}K`}
</div> </div>
</button> </button>
</div> </div>

Просмотреть файл

@@ -19,6 +19,7 @@ import {ModalData} from 'types/actions';
import {RhsState} from 'types/store/rhs'; import {RhsState} from 'types/store/rhs';
import Constants, {ModalIdentifiers, RHSStates} from 'utils/constants'; import Constants, {ModalIdentifiers, RHSStates} from 'utils/constants';
import * as Keyboard from 'utils/keyboard';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
import CreateUserGroupsModal from 'components/create_user_groups_modal'; import CreateUserGroupsModal from 'components/create_user_groups_modal';
@@ -98,15 +99,15 @@ export default class Sidebar extends React.PureComponent<Props, State> {
}; };
handleKeyDownEvent = (event: KeyboardEvent) => { handleKeyDownEvent = (event: KeyboardEvent) => {
if (Utils.isKeyPressed(event, Constants.KeyCodes.ESCAPE)) { if (Keyboard.isKeyPressed(event, Constants.KeyCodes.ESCAPE)) {
this.props.actions.clearChannelSelection(); this.props.actions.clearChannelSelection();
return; return;
} }
const ctrlOrMetaKeyPressed = Utils.cmdOrCtrlPressed(event, true); const ctrlOrMetaKeyPressed = Keyboard.cmdOrCtrlPressed(event, true);
if (ctrlOrMetaKeyPressed) { if (ctrlOrMetaKeyPressed) {
if (Utils.isKeyPressed(event, Constants.KeyCodes.FORWARD_SLASH)) { if (Keyboard.isKeyPressed(event, Constants.KeyCodes.FORWARD_SLASH)) {
event.preventDefault(); event.preventDefault();
if (this.props.isKeyBoardShortcutModalOpen) { if (this.props.isKeyBoardShortcutModalOpen) {
this.props.actions.closeModal(ModalIdentifiers.KEYBOARD_SHORTCUTS_MODAL); this.props.actions.closeModal(ModalIdentifiers.KEYBOARD_SHORTCUTS_MODAL);
@@ -116,7 +117,7 @@ export default class Sidebar extends React.PureComponent<Props, State> {
dialogType: KeyboardShortcutsModal, dialogType: KeyboardShortcutsModal,
}); });
} }
} else if (Utils.isKeyPressed(event, Constants.KeyCodes.A) && event.shiftKey) { } else if (Keyboard.isKeyPressed(event, Constants.KeyCodes.A) && event.shiftKey) {
event.preventDefault(); event.preventDefault();
this.props.actions.openModal({ this.props.actions.openModal({

Просмотреть файл

@@ -16,7 +16,7 @@ import Tooltip from 'components/tooltip';
import {DraggingState} from 'types/store'; import {DraggingState} from 'types/store';
import Constants, {A11yCustomEventTypes, DraggingStateTypes, DraggingStates, Preferences, Touched} from 'utils/constants'; import Constants, {A11yCustomEventTypes, DraggingStateTypes, DraggingStates, Preferences, Touched} from 'utils/constants';
import {t} from 'utils/i18n'; import {t} from 'utils/i18n';
import {isKeyPressed} from 'utils/utils'; import {isKeyPressed} from 'utils/keyboard';
import SidebarChannel from '../sidebar_channel'; import SidebarChannel from '../sidebar_channel';
import {SidebarCategoryHeader} from '../sidebar_category_header'; import {SidebarCategoryHeader} from '../sidebar_category_header';
import InviteMembersButton from '../invite_members_button'; import InviteMembersButton from '../invite_members_button';

Просмотреть файл

@@ -15,8 +15,9 @@ import Tooltip from 'components/tooltip';
import Constants, {RHSStates} from 'utils/constants'; import Constants, {RHSStates} from 'utils/constants';
import {wrapEmojis} from 'utils/emoji_utils'; import {wrapEmojis} from 'utils/emoji_utils';
import {cmdOrCtrlPressed} from 'utils/keyboard';
import {isDesktopApp} from 'utils/user_agent'; 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 {ChannelsAndDirectMessagesTour} from 'components/tours/onboarding_tour';
import CustomStatusEmoji from 'components/custom_status/custom_status_emoji'; import CustomStatusEmoji from 'components/custom_status/custom_status_emoji';

Просмотреть файл

@@ -17,6 +17,7 @@ import {General} from 'mattermost-redux/constants';
import {trackEvent} from 'actions/telemetry_actions'; import {trackEvent} from 'actions/telemetry_actions';
import {DraggingState} from 'types/store'; import {DraggingState} from 'types/store';
import {Constants, DraggingStates, DraggingStateTypes} from 'utils/constants'; import {Constants, DraggingStates, DraggingStateTypes} from 'utils/constants';
import * as Keyboard from 'utils/keyboard';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
import {StaticPage} from 'types/store/lhs'; import {StaticPage} from 'types/store/lhs';
@@ -314,7 +315,7 @@ export default class SidebarList extends React.PureComponent<Props, State> {
}; };
navigateChannelShortcut = (e: KeyboardEvent) => { 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(); e.preventDefault();
const staticPageIds = this.getDisplayedStaticPageIds(); const staticPageIds = this.getDisplayedStaticPageIds();
@@ -324,7 +325,7 @@ export default class SidebarList extends React.PureComponent<Props, State> {
const curIndex = allIds.indexOf(curSelectedId); const curIndex = allIds.indexOf(curSelectedId);
let nextIndex; let nextIndex;
if (Utils.isKeyPressed(e, Constants.KeyCodes.DOWN)) { if (Keyboard.isKeyPressed(e, Constants.KeyCodes.DOWN)) {
nextIndex = curIndex + 1; nextIndex = curIndex + 1;
} else { } else {
nextIndex = curIndex - 1; nextIndex = curIndex - 1;
@@ -335,13 +336,13 @@ export default class SidebarList extends React.PureComponent<Props, State> {
if (nextIndex >= staticPageIds.length) { if (nextIndex >= staticPageIds.length) {
this.scrollToChannel(nextId); 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); this.props.handleOpenMoreDirectChannelsModal(e);
} }
}; };
navigateUnreadChannelShortcut = (e: KeyboardEvent) => { 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(); e.preventDefault();
const allChannelIds = this.getDisplayedChannelIds(); const allChannelIds = this.getDisplayedChannelIds();
@@ -356,7 +357,7 @@ export default class SidebarList extends React.PureComponent<Props, State> {
} }
let direction = 0; let direction = 0;
if (Utils.isKeyPressed(e, Constants.KeyCodes.UP)) { if (Keyboard.isKeyPressed(e, Constants.KeyCodes.UP)) {
direction = -1; direction = -1;
} else { } else {
direction = 1; direction = 1;

Просмотреть файл

@@ -13,7 +13,8 @@ import {RhsState} from 'types/store/rhs';
import {trackEvent} from 'actions/telemetry_actions.jsx'; import {trackEvent} from 'actions/telemetry_actions.jsx';
import Constants from 'utils/constants'; 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 FileUploadOverlay from 'components/file_upload_overlay';
import RhsThread from 'components/rhs_thread'; import RhsThread from 'components/rhs_thread';

Просмотреть файл

@@ -75,10 +75,8 @@ export {
filterEmptyOptions, filterEmptyOptions,
} from 'utils/apps'; } from 'utils/apps';
import { import {isMac} from 'utils/user_agent';
isMac, import {localizeAndFormatMessage} from 'utils/utils';
localizeAndFormatMessage,
} from 'utils/utils';
export type Store = { export type Store = {
dispatch: DispatchFunc; dispatch: DispatchFunc;

Просмотреть файл

@@ -14,7 +14,6 @@ import {AutocompleteSuggestion, CommandArgs} from '@mattermost/types/integration
import globalStore from 'stores/redux_store'; import globalStore from 'stores/redux_store';
import * as UserAgent from 'utils/user_agent'; import * as UserAgent from 'utils/user_agent';
import * as Utils from 'utils/utils';
import {Constants} from 'utils/constants'; import {Constants} from 'utils/constants';
import Suggestion from '../suggestion'; import Suggestion from '../suggestion';
@@ -229,7 +228,7 @@ export default class CommandProvider extends Provider {
let matches: AutocompleteSuggestion[] = []; let matches: AutocompleteSuggestion[] = [];
let cmd = 'Ctrl'; let cmd = 'Ctrl';
if (Utils.isMac()) { if (UserAgent.isMac()) {
cmd = '⌘'; cmd = '⌘';
} }

Просмотреть файл

@@ -9,6 +9,7 @@ import type {Locale} from 'date-fns';
import Suggestion from '../suggestion.jsx'; import Suggestion from '../suggestion.jsx';
import * as Keyboard from 'utils/keyboard';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
@@ -27,9 +28,9 @@ export default class SearchDateSuggestion extends Suggestion {
}; };
handleKeyDown = (e: KeyboardEvent) => { 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}); this.setState({datePickerFocused: true});
} else if (Utils.isKeyPressed(e, Constants.KeyCodes.ESCAPE)) { } else if (Keyboard.isKeyPressed(e, Constants.KeyCodes.ESCAPE)) {
this.props.handleEscape(); this.props.handleEscape();
} }
}; };

Просмотреть файл

@@ -8,6 +8,7 @@ import EventEmitter from 'mattermost-redux/utils/event_emitter';
import QuickInput from 'components/quick_input'; import QuickInput from 'components/quick_input';
import Constants, {A11yCustomEventTypes} from 'utils/constants'; import Constants, {A11yCustomEventTypes} from 'utils/constants';
import * as Keyboard from 'utils/keyboard';
import * as UserAgent from 'utils/user_agent'; import * as UserAgent from 'utils/user_agent';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
@@ -497,7 +498,7 @@ export default class SuggestionBox extends React.PureComponent {
if (finish && this.props.onKeyPress) { if (finish && this.props.onKeyPress) {
let ke = e; let ke = e;
if (!e || Utils.isKeyPressed(e, Constants.KeyCodes.TAB)) { if (!e || Keyboard.isKeyPressed(e, Constants.KeyCodes.TAB)) {
ke = new KeyboardEvent('keydown', { ke = new KeyboardEvent('keydown', {
bubbles: true, cancelable: true, keyCode: 13, bubbles: true, cancelable: true, keyCode: 13,
}); });
@@ -582,13 +583,13 @@ export default class SuggestionBox extends React.PureComponent {
handleKeyDown = (e) => { handleKeyDown = (e) => {
if ((this.props.openWhenEmpty || this.props.value) && this.hasSuggestions()) { if ((this.props.openWhenEmpty || this.props.value) && this.hasSuggestions()) {
const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey; const ctrlOrMetaKeyPressed = e.ctrlKey || e.metaKey;
if (Utils.isKeyPressed(e, KeyCodes.UP)) { if (Keyboard.isKeyPressed(e, KeyCodes.UP)) {
this.selectPrevious(); this.selectPrevious();
e.preventDefault(); e.preventDefault();
} else if (Utils.isKeyPressed(e, KeyCodes.DOWN)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.DOWN)) {
this.selectNext(); this.selectNext();
e.preventDefault(); 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 = ''; let matchedPretext = '';
for (let i = 0; i < this.state.terms.length; i++) { for (let i = 0; i < this.state.terms.length; i++) {
if (this.state.terms[i] === this.state.selection) { if (this.state.terms[i] === this.state.selection) {
@@ -611,7 +612,7 @@ export default class SuggestionBox extends React.PureComponent {
this.props.onKeyDown(e); this.props.onKeyDown(e);
} }
e.preventDefault(); e.preventDefault();
} else if (Utils.isKeyPressed(e, KeyCodes.ESCAPE)) { } else if (Keyboard.isKeyPressed(e, KeyCodes.ESCAPE)) {
this.clear(); this.clear();
this.setState({presentationType: 'text'}); this.setState({presentationType: 'text'});
e.preventDefault(); e.preventDefault();

Просмотреть файл

@@ -10,8 +10,8 @@ import {ActionResult} from 'mattermost-redux/types/actions';
import {reconnect} from 'actions/websocket_actions.jsx'; import {reconnect} from 'actions/websocket_actions.jsx';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import {cmdOrCtrlPressed, isKeyPressed} from 'utils/keyboard';
import {isIosSafari} from 'utils/user_agent'; import {isIosSafari} from 'utils/user_agent';
import {cmdOrCtrlPressed, isKeyPressed} from 'utils/utils';
import {makeAsyncComponent} from 'components/async_load'; import {makeAsyncComponent} from 'components/async_load';
import ChannelController from 'components/channel_layout/channel_controller'; import ChannelController from 'components/channel_layout/channel_controller';

Просмотреть файл

@@ -13,6 +13,7 @@ import {Team} from '@mattermost/types/teams';
import Permissions from 'mattermost-redux/constants/permissions'; import Permissions from 'mattermost-redux/constants/permissions';
import {Constants} from 'utils/constants'; import {Constants} from 'utils/constants';
import * as Keyboard from 'utils/keyboard';
import {filterAndSortTeamsByDisplayName} from 'utils/team_utils'; import {filterAndSortTeamsByDisplayName} from 'utils/team_utils';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
@@ -71,9 +72,9 @@ export default class TeamSidebar extends React.PureComponent<Props, State> {
} }
switchToPrevOrNextTeam = (e: KeyboardEvent, currentTeamId: string, teams: Team[]) => { 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(); 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 pos = teams.findIndex((team: Team) => team.id === currentTeamId);
const newPos = pos + delta; const newPos = pos + delta;
@@ -107,7 +108,7 @@ export default class TeamSidebar extends React.PureComponent<Props, State> {
]; ];
for (const idx in digits) { 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(); e.preventDefault();
// prevents reloading the current team, while still capturing the keyboard shortcut // prevents reloading the current team, while still capturing the keyboard shortcut

Просмотреть файл

@@ -8,7 +8,7 @@ import {isEmpty} from 'lodash';
import {PlaylistCheckIcon} from '@mattermost/compass-icons/components'; 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 {getThreadCountsInCurrentTeam} from 'mattermost-redux/selectors/entities/threads';
import {getThreads, markAllThreadsInTeamRead} from 'mattermost-redux/actions/threads'; import {getThreads, markAllThreadsInTeamRead} from 'mattermost-redux/actions/threads';
import {trackEvent} from 'actions/telemetry_actions'; import {trackEvent} from 'actions/telemetry_actions';
@@ -80,7 +80,7 @@ const ThreadList = ({
return; return;
} }
const comboKeyPressed = e.altKey || e.metaKey || e.shiftKey || e.ctrlKey; 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; return;
} }
@@ -94,7 +94,7 @@ const ThreadList = ({
let threadIdToSelect = 0; let threadIdToSelect = 0;
if (selectedThreadId) { if (selectedThreadId) {
const selectedThreadIndex = data.indexOf(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) { if (selectedThreadIndex < data.length - 1) {
threadIdToSelect = selectedThreadIndex + 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) { if (selectedThreadIndex > 0) {
threadIdToSelect = selectedThreadIndex - 1; threadIdToSelect = selectedThreadIndex - 1;
} else { } else {

Просмотреть файл

@@ -7,8 +7,9 @@ import {RouteComponentProps} from 'react-router-dom';
import {Preferences} from 'mattermost-redux/constants'; import {Preferences} from 'mattermost-redux/constants';
import {isKeyPressed} from 'utils/keyboard';
import {isIdNotPost, getNewMessageIndex} from 'utils/post_utils'; import {isIdNotPost, getNewMessageIndex} from 'utils/post_utils';
import {isKeyPressed, localizeMessage} from 'utils/utils'; import {localizeMessage} from 'utils/utils';
import {isToday} from 'utils/datetime'; import {isToday} from 'utils/datetime';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import {getHistory} from 'utils/browser_history'; import {getHistory} from 'utils/browser_history';

Просмотреть файл

@@ -13,7 +13,7 @@ import GenericModal from 'components/generic_modal';
import NextIcon from 'components/widgets/icons/fa_next_icon'; import NextIcon from 'components/widgets/icons/fa_next_icon';
import FormattedMarkdownMessage from 'components/formatted_markdown_message'; import FormattedMarkdownMessage from 'components/formatted_markdown_message';
import {Constants, ModalIdentifiers, Preferences} from 'utils/constants'; 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 './collapsed_reply_threads_modal.scss';
import {AutoTourStatus, TTNameMapToATStatusKey, TutorialTourName} from '../../constant'; import {AutoTourStatus, TTNameMapToATStatusKey, TutorialTourName} from '../../constant';
@@ -26,7 +26,7 @@ function CollapsedReplyThreadsModal(props: Props) {
const dispatch = useDispatch(); const dispatch = useDispatch();
const currentUserId = useSelector(getCurrentUserId); const currentUserId = useSelector(getCurrentUserId);
const handleKeyDown = useCallback((e: KeyboardEvent) => { const handleKeyDown = useCallback((e: KeyboardEvent) => {
if (Utils.isKeyPressed(e, Constants.KeyCodes.ENTER)) { if (Keyboard.isKeyPressed(e, Constants.KeyCodes.ENTER)) {
onNext(); onNext();
} }
}, []); }, []);

Просмотреть файл

@@ -7,6 +7,7 @@ import {Modal} from 'react-bootstrap';
import {FormattedMessage} from 'react-intl'; import {FormattedMessage} from 'react-intl';
import * as Keyboard from 'utils/keyboard';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
import {CustomGroupPatch, Group} from '@mattermost/types/groups'; import {CustomGroupPatch, Group} from '@mattermost/types/groups';
@@ -52,7 +53,7 @@ const UpdateUserGroupModal = (props: Props) => {
}, [name, mention, hasUpdated, saving]); }, [name, mention, hasUpdated, saving]);
const handleKeyDown = useCallback((e: KeyboardEvent) => { const handleKeyDown = useCallback((e: KeyboardEvent) => {
if (Utils.isKeyPressed(e, Constants.KeyCodes.ENTER) && isSaveEnabled()) { if (Keyboard.isKeyPressed(e, Constants.KeyCodes.ENTER) && isSaveEnabled()) {
patchGroup(); patchGroup();
} }
}, [name, mention, hasUpdated, saving]); }, [name, mention, hasUpdated, saving]);

Просмотреть файл

@@ -14,7 +14,7 @@ import {Group} from '@mattermost/types/groups';
import {ActionResult} from 'mattermost-redux/types/actions'; import {ActionResult} from 'mattermost-redux/types/actions';
import {shouldFocusMainTextbox} from 'utils/post_utils'; 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 Constants, {A11yClassNames, A11yCustomEventTypes, A11yFocusEventDetail, ModalIdentifiers} from 'utils/constants';
import {QuickInput} from 'components/quick_input/quick_input'; import {QuickInput} from 'components/quick_input/quick_input';
@@ -169,7 +169,7 @@ const UserGroupPopover = (props: Props) => {
const handleKeyDown = (e: React.KeyboardEvent) => { const handleKeyDown = (e: React.KeyboardEvent) => {
if (shouldFocusMainTextbox(e, document.activeElement)) { if (shouldFocusMainTextbox(e, document.activeElement)) {
hide(); hide();
} else if (Utils.isKeyPressed(e, Constants.KeyCodes.ESCAPE)) { } else if (Keyboard.isKeyPressed(e, Constants.KeyCodes.ESCAPE)) {
returnFocus(); returnFocus();
} }
}; };

Просмотреть файл

@@ -8,10 +8,10 @@ import AdvancedSettingsDisplay from 'components/user_settings/advanced/user_sett
import {Preferences} from 'utils/constants'; import {Preferences} from 'utils/constants';
import {TestHelper} from 'utils/test_helper'; import {TestHelper} from 'utils/test_helper';
import {isMac} from 'utils/utils'; import {isMac} from 'utils/user_agent';
jest.mock('actions/global_actions'); jest.mock('actions/global_actions');
jest.mock('utils/utils'); jest.mock('utils/user_agent');
describe('components/user_settings/display/UserSettingsDisplay', () => { describe('components/user_settings/display/UserSettingsDisplay', () => {
const user = TestHelper.getUserMock({ const user = TestHelper.getUserMock({

Просмотреть файл

@@ -10,7 +10,8 @@ import {emitUserLoggedOutEvent} from 'actions/global_actions';
import Constants, {AdvancedSections, Preferences} from 'utils/constants'; import Constants, {AdvancedSections, Preferences} from 'utils/constants';
import {t} from 'utils/i18n'; 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 SettingItemMax from 'components/setting_item_max';
import ConfirmModal from 'components/confirm_modal'; import ConfirmModal from 'components/confirm_modal';

Просмотреть файл

@@ -10,7 +10,7 @@ import SettingItemMax from 'components/setting_item_max';
import {ActionResult} from 'mattermost-redux/types/actions'; import {ActionResult} from 'mattermost-redux/types/actions';
import * as I18n from 'i18n/i18n.jsx'; import * as I18n from 'i18n/i18n.jsx';
import {isKeyPressed} from 'utils/utils'; import {isKeyPressed} from 'utils/keyboard';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import {UserProfile} from '@mattermost/types/users'; import {UserProfile} from '@mattermost/types/users';

Просмотреть файл

@@ -15,6 +15,7 @@ import {UserProfile} from '@mattermost/types/users';
import {StatusOK} from '@mattermost/types/client4'; import {StatusOK} from '@mattermost/types/client4';
import store from 'stores/redux_store.jsx'; import store from 'stores/redux_store.jsx';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import * as Keyboard from 'utils/keyboard';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
import {t} from 'utils/i18n'; import {t} from 'utils/i18n';
import ConfirmModal from 'components/confirm_modal'; import ConfirmModal from 'components/confirm_modal';
@@ -146,7 +147,7 @@ class UserSettingsModal extends React.PureComponent<Props, State> {
} }
handleKeyDown = (e: KeyboardEvent) => { 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(); e.preventDefault();
this.handleHide(); this.handleHide();
} }

Просмотреть файл

@@ -8,8 +8,8 @@ import {NotificationLevels} from 'utils/constants';
import DesktopNotificationSettings from './desktop_notification_settings'; import DesktopNotificationSettings from './desktop_notification_settings';
jest.mock('utils/utils', () => { jest.mock('utils/notification_sounds', () => {
const original = jest.requireActual('utils/utils'); const original = jest.requireActual('utils/notification_sounds');
return { return {
...original, ...original,
hasSoundOptions: jest.fn(() => true), hasSoundOptions: jest.fn(() => true),

Просмотреть файл

@@ -8,6 +8,7 @@ import {FormattedMessage} from 'react-intl';
import semver from 'semver'; import semver from 'semver';
import {NotificationLevels} from 'utils/constants'; import {NotificationLevels} from 'utils/constants';
import * as NotificationSounds from 'utils/notification_sounds';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
import {t} from 'utils/i18n'; import {t} from 'utils/i18n';
import {isDesktopApp} from 'utils/user_agent'; import {isDesktopApp} from 'utils/user_agent';
@@ -86,7 +87,7 @@ export default class DesktopNotificationSettings extends React.PureComponent<Pro
if (selectedOption && 'value' in selectedOption) { if (selectedOption && 'value' in selectedOption) {
this.props.setParentState('desktopNotificationSound', selectedOption.value); this.props.setParentState('desktopNotificationSound', selectedOption.value);
this.setState({selectedOption}); this.setState({selectedOption});
Utils.tryNotificationSound(selectedOption.value); NotificationSounds.tryNotificationSound(selectedOption.value);
} }
}; };
@@ -123,7 +124,7 @@ export default class DesktopNotificationSettings extends React.PureComponent<Pro
} }
if (this.props.sound === 'true') { if (this.props.sound === 'true') {
const sounds = Array.from(Utils.notificationSounds.keys()); const sounds = Array.from(NotificationSounds.notificationSounds.keys());
const options = sounds.map((sound) => { const options = sounds.map((sound) => {
return {value: sound, label: sound}; return {value: sound, label: sound};
}); });
@@ -144,7 +145,7 @@ export default class DesktopNotificationSettings extends React.PureComponent<Pro
} }
} }
if (Utils.hasSoundOptions()) { if (NotificationSounds.hasSoundOptions()) {
soundSection = ( soundSection = (
<fieldset> <fieldset>
<legend className='form-legend'> <legend className='form-legend'>
@@ -344,7 +345,7 @@ export default class DesktopNotificationSettings extends React.PureComponent<Pro
buildMinimizedSetting = () => { buildMinimizedSetting = () => {
let formattedMessageProps; let formattedMessageProps;
const hasSoundOption = Utils.hasSoundOptions(); const hasSoundOption = NotificationSounds.hasSoundOptions();
if (this.props.activity === NotificationLevels.MENTION) { if (this.props.activity === NotificationLevels.MENTION) {
if (hasSoundOption && this.props.sound !== 'false') { if (hasSoundOption && this.props.sound !== 'false') {
formattedMessageProps = { formattedMessageProps = {

Просмотреть файл

@@ -8,6 +8,7 @@ import * as UserUtils from 'mattermost-redux/utils/user_utils';
import {trackEvent} from 'actions/telemetry_actions.jsx'; import {trackEvent} from 'actions/telemetry_actions.jsx';
import Constants from 'utils/constants'; import Constants from 'utils/constants';
import * as Keyboard from 'utils/keyboard';
import {isMobile} from 'utils/user_agent'; import {isMobile} from 'utils/user_agent';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
import ConfirmModal from 'components/confirm_modal'; import ConfirmModal from 'components/confirm_modal';
@@ -263,7 +264,7 @@ export default class UserAccessTokenSection extends React.PureComponent<Props, S
}; };
saveTokenKeyPress = (e: React.KeyboardEvent) => { saveTokenKeyPress = (e: React.KeyboardEvent) => {
if (Utils.isKeyPressed(e, Constants.KeyCodes.ENTER)) { if (Keyboard.isKeyPressed(e, Constants.KeyCodes.ENTER)) {
this.confirmCreateToken(); this.confirmCreateToken();
} }
}; };

Просмотреть файл

@@ -4,6 +4,7 @@
import React, {CSSProperties} from 'react'; import React, {CSSProperties} from 'react';
import classNames from 'classnames'; import classNames from 'classnames';
import * as Keyboard from 'utils/keyboard';
import * as Utils from 'utils/utils'; import * as Utils from 'utils/utils';
import {showMobileSubMenuModal} from 'actions/global_actions'; import {showMobileSubMenuModal} from 'actions/global_actions';
@@ -113,7 +114,7 @@ export default class SubMenuItem extends React.PureComponent<Props, State> {
}; };
handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => { handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {
if (Utils.isKeyPressed(event, Constants.KeyCodes.ENTER)) { if (Keyboard.isKeyPressed(event, Constants.KeyCodes.ENTER)) {
if (this.props.action) { if (this.props.action) {
this.onClick(event); this.onClick(event);
} else { } else {
@@ -121,7 +122,7 @@ export default class SubMenuItem extends React.PureComponent<Props, State> {
} }
} }
if (Utils.isKeyPressed(event, Constants.KeyCodes.RIGHT)) { if (Keyboard.isKeyPressed(event, Constants.KeyCodes.RIGHT)) {
if (this.props.direction === 'right') { if (this.props.direction === 'right') {
this.show(); this.show();
} else { } else {
@@ -129,7 +130,7 @@ export default class SubMenuItem extends React.PureComponent<Props, State> {
} }
} }
if (Utils.isKeyPressed(event, Constants.KeyCodes.LEFT)) { if (Keyboard.isKeyPressed(event, Constants.KeyCodes.LEFT)) {
if (this.props.direction === 'left') { if (this.props.direction === 'left') {
this.show(); this.show();
} else { } else {

53
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;
}

Просмотреть файл

@@ -2,8 +2,8 @@
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
import Constants, {EventTypes, A11yClassNames, A11yAttributeNames, A11yCustomEventTypes, isA11yFocusEventDetail} from 'utils/constants'; import Constants, {EventTypes, A11yClassNames, A11yAttributeNames, A11yCustomEventTypes, isA11yFocusEventDetail} from 'utils/constants';
import {isKeyPressed, cmdOrCtrlPressed, isMac} from 'utils/utils'; import {isKeyPressed, cmdOrCtrlPressed} from 'utils/keyboard';
import {isDesktopApp} from 'utils/user_agent'; import {isDesktopApp, isMac} from 'utils/user_agent';
const listenerOptions = { const listenerOptions = {
capture: true, capture: true,

145
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);
});
});

35
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];
}

Просмотреть файл

@@ -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());
}

Просмотреть файл

@@ -42,11 +42,11 @@ import {getIsMobileView} from 'selectors/views/browser';
import {GlobalState} from 'types/store'; import {GlobalState} from 'types/store';
import Constants, {PostListRowListIds, Preferences} from 'utils/constants'; import Constants, {PostListRowListIds, Preferences} from 'utils/constants';
import * as Keyboard from 'utils/keyboard';
import {formatWithRenderer} from 'utils/markdown'; import {formatWithRenderer} from 'utils/markdown';
import MentionableRenderer from 'utils/markdown/mentionable_renderer'; import MentionableRenderer from 'utils/markdown/mentionable_renderer';
import {allAtMentions} from 'utils/text_formatting'; import {allAtMentions} from 'utils/text_formatting';
import {isMobile} from 'utils/user_agent'; import {isMobile} from 'utils/user_agent';
import * as Utils from 'utils/utils';
import EmojiMap from './emoji_map'; import EmojiMap from './emoji_map';
import * as Emoticons from './emoticons'; import * as Emoticons from './emoticons';
@@ -236,7 +236,7 @@ export function shouldFocusMainTextbox(e: React.KeyboardEvent | KeyboardEvent, a
} }
// Focus if it is an attempted paste // 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; return true;
} }
@@ -257,7 +257,7 @@ export function shouldFocusMainTextbox(e: React.KeyboardEvent | KeyboardEvent, a
// Do not focus when pressing space on link elements // Do not focus when pressing space on link elements
const spaceKeepFocusTags = ['BUTTON', 'A']; 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; return false;
} }
@@ -311,7 +311,7 @@ export function postMessageOnKeyPress(
} }
// Only ENTER sends, unless shift or alt key pressed. // 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}; return {allowSending: false};
} }

Просмотреть файл

@@ -142,6 +142,10 @@ export function isMac(): boolean {
return userAgent().indexOf('Macintosh') !== -1; return userAgent().indexOf('Macintosh') !== -1;
} }
export function isLinux(): boolean {
return navigator.platform.toUpperCase().indexOf('LINUX') >= 0;
}
export function isWindows7(): boolean { export function isWindows7(): boolean {
const appVersion = navigator.appVersion; const appVersion = navigator.appVersion;

Просмотреть файл

@@ -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', () => { describe('Utils.localizeMessage', () => {
const originalGetState = store.getState; const originalGetState = store.getState;

Просмотреть файл

@@ -1,8 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information. // See LICENSE.txt for license information.
/* eslint-disable max-lines */
import React, {LinkHTMLAttributes} from 'react'; import React, {LinkHTMLAttributes} from 'react';
import {FormattedMessage, IntlShape} from 'react-intl'; import {FormattedMessage, IntlShape} from 'react-intl';
@@ -30,7 +28,6 @@ import {
getChannel, getChannel,
getChannelsNameMapInTeam, getChannelsNameMapInTeam,
getMyChannelMemberships, getMyChannelMemberships,
getRedirectChannelNameForTeam,
} from 'mattermost-redux/selectors/entities/channels'; } from 'mattermost-redux/selectors/entities/channels';
import {getPost} from 'mattermost-redux/selectors/entities/posts'; import {getPost} from 'mattermost-redux/selectors/entities/posts';
import {getBool, getTeammateNameDisplaySetting, Theme, isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences'; 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 {blendColors, changeOpacity} from 'mattermost-redux/utils/theme_utils';
import {displayUsername, isSystemAdmin} from 'mattermost-redux/utils/user_utils'; import {displayUsername, isSystemAdmin} from 'mattermost-redux/utils/user_utils';
import { import {
getCurrentRelativeTeamUrl,
getCurrentTeam,
getCurrentTeamId,
getTeam,
getTeamByName, getTeamByName,
getTeamMemberships, getTeamMemberships,
isTeamSameWithCurrentTeam, isTeamSameWithCurrentTeam,
@@ -50,14 +43,9 @@ import {
import {addUserToTeam} from 'actions/team_actions'; import {addUserToTeam} from 'actions/team_actions';
import {searchForTerm} from 'actions/post_actions'; import {searchForTerm} from 'actions/post_actions';
import {getHistory} from 'utils/browser_history'; import {getHistory} from 'utils/browser_history';
import * as Keyboard from 'utils/keyboard';
import * as UserAgent from 'utils/user_agent'; import * as UserAgent from 'utils/user_agent';
import {isDesktopApp} 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 {t} from 'utils/i18n';
import store from 'stores/redux_store.jsx'; import store from 'stores/redux_store.jsx';
@@ -108,14 +96,6 @@ export enum TimeInformation {
export type TimeUnit = Exclude<TimeInformation, TimeInformation.FUTURE | TimeInformation.PAST>; export type TimeUnit = Exclude<TimeInformation, TimeInformation.FUTURE | TimeInformation.PAST>;
export type TimeDirection = TimeInformation.FUTURE | TimeInformation.PAST; 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 { export function createSafeId(prop: {props: {defaultMessage: string}} | string): string | undefined {
let str = ''; let str = '';
@@ -128,42 +108,14 @@ export function createSafeId(prop: {props: {defaultMessage: string}} | string):
return str.replace(new RegExp(' ', 'g'), '_'); 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 * 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 { export function isUnhandledLineBreakKeyCombo(e: React.KeyboardEvent | KeyboardEvent): boolean {
return 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.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<TextboxElemen
return newValue; return newValue;
} }
export function isInRole(roles: string, inRole: string): boolean {
if (roles) {
const parts = roles.split(' ');
for (let i = 0; i < parts.length; i++) {
if (parts[i] === inRole) {
return true;
}
}
}
return false;
}
export function getTeamRelativeUrl(team: Team) {
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;
}
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());
}
export function getDateForUnixTicks(ticks: number): Date { export function getDateForUnixTicks(ticks: number): Date {
return new Date(ticks); return new Date(ticks);
} }