mirror of
https://github.com/danog/telegram-tt.git
synced 2024-11-30 04:39:00 +01:00
Various UI fixes (#1840)
This commit is contained in:
parent
2a9b516142
commit
67da677b1b
@ -76,19 +76,20 @@ const Avatar: FC<OwnProps> = ({
|
||||
const lang = useLang();
|
||||
|
||||
let content: string | undefined = '';
|
||||
const author = user ? getUserFullName(user) : (chat ? getChatTitle(lang, chat) : text);
|
||||
|
||||
if (isSavedMessages) {
|
||||
content = <i className={buildClassName(cn.icon, 'icon-avatar-saved-messages')} />;
|
||||
content = <i className={buildClassName(cn.icon, 'icon-avatar-saved-messages')} aria-label={author} />;
|
||||
} else if (isDeleted) {
|
||||
content = <i className={buildClassName(cn.icon, 'icon-avatar-deleted-account')} />;
|
||||
content = <i className={buildClassName(cn.icon, 'icon-avatar-deleted-account')} aria-label={author} />;
|
||||
} else if (isReplies) {
|
||||
content = <i className={buildClassName(cn.icon, 'icon-reply-filled')} />;
|
||||
content = <i className={buildClassName(cn.icon, 'icon-reply-filled')} aria-label={author} />;
|
||||
} else if (blobUrl) {
|
||||
content = (
|
||||
<img
|
||||
src={blobUrl}
|
||||
className={buildClassName(cn.img, 'avatar-media', transitionClassNames)}
|
||||
alt=""
|
||||
alt={author}
|
||||
decoding="async"
|
||||
/>
|
||||
);
|
||||
@ -125,7 +126,12 @@ const Avatar: FC<OwnProps> = ({
|
||||
const senderId = (user || chat) && (user || chat)!.id;
|
||||
|
||||
return (
|
||||
<div className={fullClassName} onClick={handleClick} data-test-sender-id={IS_TEST ? senderId : undefined}>
|
||||
<div
|
||||
className={fullClassName}
|
||||
onClick={handleClick}
|
||||
data-test-sender-id={IS_TEST ? senderId : undefined}
|
||||
aria-label={typeof content === 'string' ? author : undefined}
|
||||
>
|
||||
{typeof content === 'string' ? renderText(content, [size === 'jumbo' ? 'hq_emoji' : 'emoji']) : content}
|
||||
</div>
|
||||
);
|
||||
|
@ -5,11 +5,11 @@ import { getActions, withGlobal } from '../../global';
|
||||
|
||||
import { LeftColumnContent, SettingsScreens } from '../../types';
|
||||
|
||||
import { IS_MAC_OS, LAYERS_ANIMATION_NAME } from '../../util/environment';
|
||||
import { LAYERS_ANIMATION_NAME } from '../../util/environment';
|
||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||
import getKeyFromEvent from '../../util/getKeyFromEvent';
|
||||
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
|
||||
import { useResize } from '../../hooks/useResize';
|
||||
import { useHotkeys } from '../../hooks/useHotkeys';
|
||||
|
||||
import Transition from '../ui/Transition';
|
||||
import LeftMain from './main/LeftMain';
|
||||
@ -25,6 +25,7 @@ type StateProps = {
|
||||
activeChatFolder: number;
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
leftColumnWidth?: number;
|
||||
currentUserId?: string;
|
||||
};
|
||||
|
||||
enum ContentType {
|
||||
@ -47,6 +48,7 @@ const LeftColumn: FC<StateProps> = ({
|
||||
activeChatFolder,
|
||||
shouldSkipHistoryAnimations,
|
||||
leftColumnWidth,
|
||||
currentUserId,
|
||||
}) => {
|
||||
const {
|
||||
setGlobalSearchQuery,
|
||||
@ -57,6 +59,7 @@ const LeftColumn: FC<StateProps> = ({
|
||||
clearTwoFaError,
|
||||
setLeftColumnWidth,
|
||||
resetLeftColumnWidth,
|
||||
openChat,
|
||||
} = getActions();
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
@ -255,25 +258,25 @@ const LeftColumn: FC<StateProps> = ({
|
||||
[activeChatFolder, content, handleReset],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const handleHotkeySearch = useCallback((e: KeyboardEvent) => {
|
||||
if (content === LeftColumnContent.GlobalSearch) {
|
||||
return undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
if (((IS_MAC_OS && e.metaKey) || (!IS_MAC_OS && e.ctrlKey)) && e.shiftKey && getKeyFromEvent(e) === 'f') {
|
||||
e.preventDefault();
|
||||
setContent(LeftColumnContent.GlobalSearch);
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown, false);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown, false);
|
||||
};
|
||||
e.preventDefault();
|
||||
setContent(LeftColumnContent.GlobalSearch);
|
||||
}, [content]);
|
||||
|
||||
const handleHotkeySavedMessages = useCallback((e: KeyboardEvent) => {
|
||||
e.preventDefault();
|
||||
openChat({ id: currentUserId });
|
||||
}, [currentUserId, openChat]);
|
||||
|
||||
useHotkeys([
|
||||
['mod+shift+F', handleHotkeySearch],
|
||||
['mod+shift+S', handleHotkeySavedMessages],
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
clearTwoFaError();
|
||||
|
||||
@ -386,9 +389,16 @@ export default memo(withGlobal(
|
||||
},
|
||||
shouldSkipHistoryAnimations,
|
||||
leftColumnWidth,
|
||||
currentUserId,
|
||||
} = global;
|
||||
|
||||
return {
|
||||
searchQuery: query, searchDate: date, activeChatFolder, shouldSkipHistoryAnimations, leftColumnWidth,
|
||||
searchQuery: query,
|
||||
searchDate: date,
|
||||
activeChatFolder,
|
||||
shouldSkipHistoryAnimations,
|
||||
leftColumnWidth,
|
||||
currentUserId,
|
||||
};
|
||||
},
|
||||
)(LeftColumn));
|
||||
|
@ -19,6 +19,7 @@ import usePrevious from '../../../hooks/usePrevious';
|
||||
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||
import { useFolderManagerForOrderedIds } from '../../../hooks/useFolderManager';
|
||||
import { useChatAnimationType } from './hooks';
|
||||
import { HotkeyItem, useHotkeys } from '../../../hooks/useHotkeys';
|
||||
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
import Loading from '../../ui/Loading';
|
||||
@ -74,14 +75,29 @@ const ChatList: FC<OwnProps> = ({
|
||||
|
||||
const [viewportIds, getMore] = useInfiniteScroll(undefined, orderedIds, undefined, CHAT_LIST_SLICE);
|
||||
|
||||
// Support <Cmd>+<Digit> and <Alt>+<Up/Down> to navigate between chats
|
||||
// Support <Alt>+<Up/Down> to navigate between chats
|
||||
const hotkeys: HotkeyItem[] = [];
|
||||
if (isActive && orderedIds?.length) {
|
||||
hotkeys.push(['alt+ArrowUp', (e: KeyboardEvent) => {
|
||||
e.preventDefault();
|
||||
openNextChat({ targetIndexDelta: -1, orderedIds });
|
||||
}]);
|
||||
hotkeys.push(['alt+ArrowDown', (e: KeyboardEvent) => {
|
||||
e.preventDefault();
|
||||
openNextChat({ targetIndexDelta: 1, orderedIds });
|
||||
}]);
|
||||
}
|
||||
|
||||
useHotkeys(hotkeys);
|
||||
|
||||
// Support <Cmd>+<Digit> to navigate between chats
|
||||
useEffect(() => {
|
||||
if (!isActive || !orderedIds) {
|
||||
if (!isActive || !orderedIds || !IS_PWA) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
if (IS_PWA && ((IS_MAC_OS && e.metaKey) || (!IS_MAC_OS && e.ctrlKey)) && e.code.startsWith('Digit')) {
|
||||
if (((IS_MAC_OS && e.metaKey) || (!IS_MAC_OS && e.ctrlKey)) && e.code.startsWith('Digit')) {
|
||||
const [, digit] = e.code.match(/Digit(\d)/) || [];
|
||||
if (!digit) return;
|
||||
|
||||
@ -90,14 +106,6 @@ const ChatList: FC<OwnProps> = ({
|
||||
|
||||
openChat({ id: orderedIds![position], shouldReplaceHistory: true });
|
||||
}
|
||||
|
||||
if (e.altKey) {
|
||||
const targetIndexDelta = e.key === 'ArrowDown' ? 1 : e.key === 'ArrowUp' ? -1 : undefined;
|
||||
if (!targetIndexDelta) return;
|
||||
|
||||
e.preventDefault();
|
||||
openNextChat({ targetIndexDelta, orderedIds });
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown);
|
||||
|
@ -96,7 +96,6 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
}) => {
|
||||
const {
|
||||
openChat,
|
||||
openTipsChat,
|
||||
setGlobalSearchDate,
|
||||
setSettingOption,
|
||||
setGlobalSearchChatId,
|
||||
@ -196,8 +195,8 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
||||
}, []);
|
||||
|
||||
const handleOpenTipsChat = useCallback(() => {
|
||||
openTipsChat({ langCode: lang.code });
|
||||
}, [lang.code, openTipsChat]);
|
||||
openChatByUsername({ username: lang('Settings.TipsUsername') });
|
||||
}, [lang, openChatByUsername]);
|
||||
|
||||
const isSearchFocused = (
|
||||
Boolean(globalSearchChatId)
|
||||
|
@ -4,7 +4,6 @@ import React, {
|
||||
useRef,
|
||||
useCallback,
|
||||
useState,
|
||||
useEffect,
|
||||
} from '../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../global';
|
||||
|
||||
@ -13,9 +12,8 @@ import { MAIN_THREAD_ID } from '../../api/types';
|
||||
import { IAnchorPosition, ManagementScreens } from '../../types';
|
||||
|
||||
import {
|
||||
ARE_CALLS_SUPPORTED, IS_MAC_OS, IS_PWA, IS_SINGLE_COLUMN_LAYOUT,
|
||||
ARE_CALLS_SUPPORTED, IS_PWA, IS_SINGLE_COLUMN_LAYOUT,
|
||||
} from '../../util/environment';
|
||||
import getKeyFromEvent from '../../util/getKeyFromEvent';
|
||||
import {
|
||||
isChatBasicGroup, isChatChannel, isChatSuperGroup, isUserId,
|
||||
} from '../../global/helpers';
|
||||
@ -29,6 +27,7 @@ import {
|
||||
selectIsRightColumnShown,
|
||||
} from '../../global/selectors';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import { useHotkeys } from '../../hooks/useHotkeys';
|
||||
|
||||
import Button from '../ui/Button';
|
||||
import HeaderMenuContainer from './HeaderMenuContainer.async';
|
||||
@ -143,27 +142,19 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
||||
requestCall({ userId: chatId });
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!canSearch) {
|
||||
return undefined;
|
||||
const handleHotkeySearchClick = useCallback((e: KeyboardEvent) => {
|
||||
if (!canSearch || !IS_PWA || e.shiftKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
function handleKeyDown(e: KeyboardEvent) {
|
||||
if (
|
||||
IS_PWA && ((IS_MAC_OS && e.metaKey) || (!IS_MAC_OS && e.ctrlKey)) && !e.shiftKey && getKeyFromEvent(e) === 'f'
|
||||
) {
|
||||
e.preventDefault();
|
||||
handleSearchClick();
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleKeyDown, false);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleKeyDown, false);
|
||||
};
|
||||
e.preventDefault();
|
||||
handleSearchClick();
|
||||
}, [canSearch, handleSearchClick]);
|
||||
|
||||
useHotkeys([
|
||||
['meta+F', handleHotkeySearchClick],
|
||||
]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
return (
|
||||
|
@ -253,6 +253,7 @@
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
user-select: none;
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
|
@ -1149,19 +1149,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
{formatVoiceRecordDuration(currentRecordTime - startRecordTimeRef.current!)}
|
||||
</span>
|
||||
)}
|
||||
<StickerTooltip
|
||||
chatId={chatId}
|
||||
threadId={threadId}
|
||||
isOpen={isStickerTooltipOpen}
|
||||
onStickerSelect={handleStickerSelect}
|
||||
/>
|
||||
<EmojiTooltip
|
||||
isOpen={isEmojiTooltipOpen}
|
||||
emojis={filteredEmojis}
|
||||
onClose={closeEmojiTooltip}
|
||||
onEmojiSelect={insertEmoji}
|
||||
addRecentEmoji={addRecentEmoji}
|
||||
/>
|
||||
<AttachMenu
|
||||
chatId={chatId}
|
||||
isButtonVisible={!activeVoiceRecording && !editingMessage}
|
||||
@ -1188,6 +1175,19 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
onClose={closeBotCommandMenu}
|
||||
/>
|
||||
)}
|
||||
<StickerTooltip
|
||||
chatId={chatId}
|
||||
threadId={threadId}
|
||||
isOpen={isStickerTooltipOpen}
|
||||
onStickerSelect={handleStickerSelect}
|
||||
/>
|
||||
<EmojiTooltip
|
||||
isOpen={isEmojiTooltipOpen}
|
||||
emojis={filteredEmojis}
|
||||
onClose={closeEmojiTooltip}
|
||||
onEmojiSelect={insertEmoji}
|
||||
addRecentEmoji={addRecentEmoji}
|
||||
/>
|
||||
<SymbolMenu
|
||||
chatId={chatId}
|
||||
threadId={threadId}
|
||||
|
@ -1,24 +1,16 @@
|
||||
import { useEffect } from '../../../lib/teact/teact';
|
||||
import { IS_MAC_OS } from '../../../util/environment';
|
||||
import getKeyFromEvent from '../../../util/getKeyFromEvent';
|
||||
import { useHotkeys } from '../../../hooks/useHotkeys';
|
||||
|
||||
const useCopySelectedMessages = (isActive: boolean, copySelectedMessages: NoneToVoidFunction) => {
|
||||
useEffect(() => {
|
||||
function handleCopy(e: KeyboardEvent) {
|
||||
if (((IS_MAC_OS && e.metaKey) || (!IS_MAC_OS && e.ctrlKey)) && getKeyFromEvent(e) === 'c') {
|
||||
e.preventDefault();
|
||||
copySelectedMessages();
|
||||
}
|
||||
function handleCopy(e: KeyboardEvent) {
|
||||
if (!isActive) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isActive) {
|
||||
document.addEventListener('keydown', handleCopy, false);
|
||||
}
|
||||
e.preventDefault();
|
||||
copySelectedMessages();
|
||||
}
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleCopy, false);
|
||||
};
|
||||
}, [copySelectedMessages, isActive]);
|
||||
useHotkeys([['meta+C', handleCopy]]);
|
||||
};
|
||||
|
||||
export default useCopySelectedMessages;
|
||||
|
@ -6,9 +6,12 @@
|
||||
|
||||
.description {
|
||||
position: relative;
|
||||
margin-top: 0.5rem;
|
||||
|
||||
&.has-image {
|
||||
margin: 1rem -0.5rem -0.375rem;
|
||||
.content-inner:not(.forwarded-message) & {
|
||||
margin: 0.5rem -0.5rem -0.375rem;
|
||||
}
|
||||
|
||||
.invoice-image {
|
||||
width: 100%;
|
||||
@ -16,6 +19,11 @@
|
||||
object-fit: cover;
|
||||
border-bottom-left-radius: var(--border-bottom-left-radius);
|
||||
border-bottom-right-radius: var(--border-bottom-right-radius);
|
||||
|
||||
.forwarded-message & {
|
||||
border-top-left-radius: var(--border-top-left-radius);
|
||||
border-top-right-radius: var(--border-top-right-radius);
|
||||
}
|
||||
}
|
||||
|
||||
.description-text {
|
||||
|
@ -72,7 +72,7 @@ const Invoice: FC<OwnProps> = ({
|
||||
<p className="title">{renderText(title)}</p>
|
||||
)}
|
||||
{text && (
|
||||
<p>{renderText(text, ['emoji', 'br'])}</p>
|
||||
<div>{renderText(text, ['emoji', 'br'])}</div>
|
||||
)}
|
||||
<div className={`description ${photoUrl ? 'has-image' : ''}`}>
|
||||
{photoUrl && (
|
||||
|
@ -163,7 +163,10 @@
|
||||
|
||||
&__filter {
|
||||
padding: 0 1rem 0.25rem 0.75rem;
|
||||
border-bottom: 1px solid var(--color-borders);
|
||||
background-color: var(--color-background);
|
||||
box-shadow: inset 0 -0.0625rem 0 0 var(--color-background-secondary-accent);
|
||||
margin-bottom: 0.625rem;
|
||||
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
flex-shrink: 0;
|
||||
|
@ -165,9 +165,9 @@ const ListItem: FC<OwnProps> = ({
|
||||
>
|
||||
<div
|
||||
className={buildClassName('ListItem-button', isTouched && 'active')}
|
||||
role="button"
|
||||
role={!isStatic ? 'button' : undefined}
|
||||
ref={buttonRef}
|
||||
tabIndex={0}
|
||||
tabIndex={!isStatic ? 0 : undefined}
|
||||
onClick={(!inactive && IS_TOUCH_ENV) ? handleClick : undefined}
|
||||
onMouseDown={handleMouseDown}
|
||||
onContextMenu={(!inactive && contextActions) ? handleContextMenu : undefined}
|
||||
|
@ -193,8 +193,6 @@ export const SCHEDULED_WHEN_ONLINE = 0x7FFFFFFE;
|
||||
export const DEFAULT_LANG_CODE = 'en';
|
||||
export const DEFAULT_LANG_PACK = 'android';
|
||||
export const LANG_PACKS = ['android', 'ios', 'tdesktop', 'macos'] as const;
|
||||
export const TIPS_USERNAME = 'TelegramTips';
|
||||
export const LOCALIZED_TIPS = ['ar', 'pt-br', 'id', 'it', 'ko', 'ms', 'pl', 'es', 'tr'];
|
||||
export const FEEDBACK_URL = 'https://bugs.telegram.org/?tag_ids=41&sort=time';
|
||||
export const LIGHT_THEME_BG_COLOR = '#A2AF8E';
|
||||
export const DARK_THEME_BG_COLOR = '#0F0F0F';
|
||||
|
@ -12,8 +12,6 @@ import {
|
||||
ARCHIVED_FOLDER_ID,
|
||||
TOP_CHAT_MESSAGES_PRELOAD_LIMIT,
|
||||
CHAT_LIST_LOAD_SLICE,
|
||||
TIPS_USERNAME,
|
||||
LOCALIZED_TIPS,
|
||||
RE_TG_LINK,
|
||||
SERVICE_NOTIFICATIONS_USER_ID,
|
||||
TMP_CHAT_ID, ALL_FOLDER_ID, DEBUG,
|
||||
@ -153,16 +151,6 @@ addActionHandler('openSupportChat', async (global, actions) => {
|
||||
}
|
||||
});
|
||||
|
||||
addActionHandler('openTipsChat', (global, actions, payload) => {
|
||||
const { langCode } = payload;
|
||||
|
||||
const usernamePostfix = langCode === 'pt-br'
|
||||
? 'BR'
|
||||
: LOCALIZED_TIPS.includes(langCode) ? (langCode as string).toUpperCase() : '';
|
||||
|
||||
actions.openChatByUsername({ username: `${TIPS_USERNAME}${usernamePostfix}` });
|
||||
});
|
||||
|
||||
addActionHandler('loadAllChats', async (global, actions, payload) => {
|
||||
const listType = payload.listType as 'active' | 'archived';
|
||||
const { onReplace } = payload;
|
||||
|
@ -6,6 +6,7 @@ import { orderBy } from '../../util/iteratees';
|
||||
import { LangFn } from '../../hooks/useLang';
|
||||
import { getServerTime } from '../../util/serverTime';
|
||||
import { prepareSearchWordsForNeedle } from '../../util/searchWords';
|
||||
import { formatPhoneNumber } from '../../util/phoneNumber';
|
||||
|
||||
const USER_COLOR_KEYS = [1, 8, 5, 2, 7, 4, 6];
|
||||
|
||||
@ -54,6 +55,10 @@ export function getUserFullName(user?: ApiUser) {
|
||||
return user.lastName;
|
||||
}
|
||||
|
||||
if (user.phoneNumber) {
|
||||
return `+${formatPhoneNumber(user.phoneNumber)}`;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -818,7 +818,7 @@ export type NonTypedActionNames = (
|
||||
'setAuthRememberMe' | 'clearAuthError' | 'uploadProfilePhoto' | 'goToAuthQrCode' | 'clearCache' |
|
||||
// chats
|
||||
'preloadTopChatMessages' | 'loadAllChats' | 'openChatWithInfo' | 'openLinkedChat' |
|
||||
'openSupportChat' | 'openTipsChat' | 'focusMessageInComments' | 'openChatByPhoneNumber' |
|
||||
'openSupportChat' | 'focusMessageInComments' | 'openChatByPhoneNumber' |
|
||||
'loadChatSettings' | 'loadFullChat' | 'loadTopChats' | 'requestChatUpdate' | 'updateChatMutedState' |
|
||||
'joinChannel' | 'leaveChannel' | 'deleteChannel' | 'toggleChatPinned' | 'toggleChatArchived' | 'toggleChatUnread' |
|
||||
'loadChatFolders' | 'loadRecommendedChatFolders' | 'editChatFolder' | 'addChatFolder' | 'deleteChatFolder' |
|
||||
|
31
src/hooks/useHotkeys.ts
Normal file
31
src/hooks/useHotkeys.ts
Normal file
@ -0,0 +1,31 @@
|
||||
// Original source from Mantine
|
||||
// https://github.com/mantinedev/mantine/blob/master/src/mantine-hooks/src/use-hotkeys/
|
||||
|
||||
import { useEffect } from '../lib/teact/teact';
|
||||
import { getHotkeyHandler, getHotkeyMatcher } from '../util/parseHotkey';
|
||||
|
||||
export { getHotkeyHandler };
|
||||
|
||||
export type HotkeyItem = [string, (event: KeyboardEvent) => void];
|
||||
|
||||
function shouldFireEvent(event: KeyboardEvent) {
|
||||
if (event.target instanceof HTMLElement) {
|
||||
return !['INPUT', 'TEXTAREA', 'SELECT'].includes(event.target.tagName);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
export function useHotkeys(hotkeys: HotkeyItem[]) {
|
||||
useEffect(() => {
|
||||
const keydownListener = (event: KeyboardEvent) => {
|
||||
hotkeys.forEach(([hotkey, handler]) => {
|
||||
if (getHotkeyMatcher(hotkey)(event) && shouldFireEvent(event)) {
|
||||
handler(event);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
document.documentElement.addEventListener('keydown', keydownListener);
|
||||
return () => document.documentElement.removeEventListener('keydown', keydownListener);
|
||||
}, [hotkeys]);
|
||||
}
|
@ -1,27 +1,17 @@
|
||||
import { useEffect } from '../lib/teact/teact';
|
||||
import { IS_MAC_OS } from '../util/environment';
|
||||
import getKeyFromEvent from '../util/getKeyFromEvent';
|
||||
import { useHotkeys } from './useHotkeys';
|
||||
import getMessageIdsForSelectedText from '../util/getMessageIdsForSelectedText';
|
||||
|
||||
const useNativeCopySelectedMessages = (copyMessagesByIds: ({ messageIds }: { messageIds?: number[] }) => void) => {
|
||||
useEffect(() => {
|
||||
function handleCopy(e: KeyboardEvent) {
|
||||
if (((IS_MAC_OS && e.metaKey) || (!IS_MAC_OS && e.ctrlKey)) && getKeyFromEvent(e) === 'c') {
|
||||
const messageIds = getMessageIdsForSelectedText();
|
||||
function handleCopy(e: KeyboardEvent) {
|
||||
const messageIds = getMessageIdsForSelectedText();
|
||||
|
||||
if (messageIds && messageIds.length > 0) {
|
||||
e.preventDefault();
|
||||
copyMessagesByIds({ messageIds });
|
||||
}
|
||||
}
|
||||
if (messageIds && messageIds.length > 0) {
|
||||
e.preventDefault();
|
||||
copyMessagesByIds({ messageIds });
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', handleCopy, false);
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('keydown', handleCopy, false);
|
||||
};
|
||||
}, [copyMessagesByIds]);
|
||||
useHotkeys([['meta+C', handleCopy]]);
|
||||
};
|
||||
|
||||
export default useNativeCopySelectedMessages;
|
||||
|
@ -1849,4 +1849,8 @@ export default {
|
||||
key: 'ChannelVisibility.Forwarding.Disabled',
|
||||
value: 'Restrict Forwarding',
|
||||
},
|
||||
'Settings.TipsUsername': {
|
||||
key: 'Settings.TipsUsername',
|
||||
value: 'TelegramTips',
|
||||
},
|
||||
} as ApiLangPack;
|
||||
|
90
src/util/parseHotkey.ts
Normal file
90
src/util/parseHotkey.ts
Normal file
@ -0,0 +1,90 @@
|
||||
// Original source from Mantine
|
||||
// https://github.com/mantinedev/mantine/blob/master/src/mantine-hooks/src/use-hotkeys/parse-hotkey.ts
|
||||
|
||||
export type KeyboardModifiers = {
|
||||
alt: boolean;
|
||||
ctrl: boolean;
|
||||
meta: boolean;
|
||||
mod: boolean;
|
||||
shift: boolean;
|
||||
};
|
||||
|
||||
export type Hotkey = KeyboardModifiers & {
|
||||
key?: string;
|
||||
};
|
||||
|
||||
type HotkeyItem = [string, (event: React.KeyboardEvent<HTMLElement>) => void];
|
||||
|
||||
type CheckHotkeyMatch = (event: KeyboardEvent) => boolean;
|
||||
|
||||
export function parseHotkey(hotkey: string): Hotkey {
|
||||
const keys = hotkey
|
||||
.toLowerCase()
|
||||
.split('+')
|
||||
.map((part) => part.trim());
|
||||
|
||||
const modifiers: KeyboardModifiers = {
|
||||
alt: keys.includes('alt'),
|
||||
ctrl: keys.includes('ctrl'),
|
||||
meta: keys.includes('meta'),
|
||||
mod: keys.includes('mod'),
|
||||
shift: keys.includes('shift'),
|
||||
};
|
||||
|
||||
const reservedKeys = ['alt', 'ctrl', 'meta', 'shift', 'mod'];
|
||||
|
||||
const freeKey = keys.find((key) => !reservedKeys.includes(key));
|
||||
|
||||
return {
|
||||
...modifiers,
|
||||
key: freeKey,
|
||||
};
|
||||
}
|
||||
|
||||
function isExactHotkey(hotkey: Hotkey, event: KeyboardEvent): boolean {
|
||||
const {
|
||||
alt, ctrl, meta, mod, shift, key,
|
||||
} = hotkey;
|
||||
const {
|
||||
altKey, ctrlKey, metaKey, shiftKey, key: pressedKey,
|
||||
} = event;
|
||||
|
||||
if (alt !== altKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mod) {
|
||||
if (!ctrlKey && !metaKey) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (ctrl !== ctrlKey) {
|
||||
return false;
|
||||
}
|
||||
if (meta !== metaKey) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (shift !== shiftKey) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Boolean(key
|
||||
&& (pressedKey.toLowerCase() === key.toLowerCase()
|
||||
|| event.code.replace('Key', '').toLowerCase() === key.toLowerCase()));
|
||||
}
|
||||
|
||||
export function getHotkeyMatcher(hotkey: string): CheckHotkeyMatch {
|
||||
return (event) => isExactHotkey(parseHotkey(hotkey), event);
|
||||
}
|
||||
|
||||
export function getHotkeyHandler(hotkeys: HotkeyItem[]) {
|
||||
return (event: React.KeyboardEvent<HTMLElement>) => {
|
||||
hotkeys.forEach(([hotkey, handler]) => {
|
||||
if (getHotkeyMatcher(hotkey)(event.nativeEvent)) {
|
||||
event.preventDefault();
|
||||
handler(event);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue
Block a user