Various UI fixes (#1840)
This commit is contained in:
parent
2a9b516142
commit
67da677b1b
@ -76,19 +76,20 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
let content: string | undefined = '';
|
let content: string | undefined = '';
|
||||||
|
const author = user ? getUserFullName(user) : (chat ? getChatTitle(lang, chat) : text);
|
||||||
|
|
||||||
if (isSavedMessages) {
|
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) {
|
} 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) {
|
} 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) {
|
} else if (blobUrl) {
|
||||||
content = (
|
content = (
|
||||||
<img
|
<img
|
||||||
src={blobUrl}
|
src={blobUrl}
|
||||||
className={buildClassName(cn.img, 'avatar-media', transitionClassNames)}
|
className={buildClassName(cn.img, 'avatar-media', transitionClassNames)}
|
||||||
alt=""
|
alt={author}
|
||||||
decoding="async"
|
decoding="async"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -125,7 +126,12 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
const senderId = (user || chat) && (user || chat)!.id;
|
const senderId = (user || chat) && (user || chat)!.id;
|
||||||
|
|
||||||
return (
|
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}
|
{typeof content === 'string' ? renderText(content, [size === 'jumbo' ? 'hq_emoji' : 'emoji']) : content}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -5,11 +5,11 @@ import { getActions, withGlobal } from '../../global';
|
|||||||
|
|
||||||
import { LeftColumnContent, SettingsScreens } from '../../types';
|
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 captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||||
import getKeyFromEvent from '../../util/getKeyFromEvent';
|
|
||||||
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
|
import useFoldersReducer from '../../hooks/reducers/useFoldersReducer';
|
||||||
import { useResize } from '../../hooks/useResize';
|
import { useResize } from '../../hooks/useResize';
|
||||||
|
import { useHotkeys } from '../../hooks/useHotkeys';
|
||||||
|
|
||||||
import Transition from '../ui/Transition';
|
import Transition from '../ui/Transition';
|
||||||
import LeftMain from './main/LeftMain';
|
import LeftMain from './main/LeftMain';
|
||||||
@ -25,6 +25,7 @@ type StateProps = {
|
|||||||
activeChatFolder: number;
|
activeChatFolder: number;
|
||||||
shouldSkipHistoryAnimations?: boolean;
|
shouldSkipHistoryAnimations?: boolean;
|
||||||
leftColumnWidth?: number;
|
leftColumnWidth?: number;
|
||||||
|
currentUserId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
enum ContentType {
|
enum ContentType {
|
||||||
@ -47,6 +48,7 @@ const LeftColumn: FC<StateProps> = ({
|
|||||||
activeChatFolder,
|
activeChatFolder,
|
||||||
shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations,
|
||||||
leftColumnWidth,
|
leftColumnWidth,
|
||||||
|
currentUserId,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
setGlobalSearchQuery,
|
setGlobalSearchQuery,
|
||||||
@ -57,6 +59,7 @@ const LeftColumn: FC<StateProps> = ({
|
|||||||
clearTwoFaError,
|
clearTwoFaError,
|
||||||
setLeftColumnWidth,
|
setLeftColumnWidth,
|
||||||
resetLeftColumnWidth,
|
resetLeftColumnWidth,
|
||||||
|
openChat,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
@ -255,25 +258,25 @@ const LeftColumn: FC<StateProps> = ({
|
|||||||
[activeChatFolder, content, handleReset],
|
[activeChatFolder, content, handleReset],
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
const handleHotkeySearch = useCallback((e: KeyboardEvent) => {
|
||||||
if (content === LeftColumnContent.GlobalSearch) {
|
if (content === LeftColumnContent.GlobalSearch) {
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyDown(e: KeyboardEvent) {
|
e.preventDefault();
|
||||||
if (((IS_MAC_OS && e.metaKey) || (!IS_MAC_OS && e.ctrlKey)) && e.shiftKey && getKeyFromEvent(e) === 'f') {
|
setContent(LeftColumnContent.GlobalSearch);
|
||||||
e.preventDefault();
|
|
||||||
setContent(LeftColumnContent.GlobalSearch);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('keydown', handleKeyDown, false);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('keydown', handleKeyDown, false);
|
|
||||||
};
|
|
||||||
}, [content]);
|
}, [content]);
|
||||||
|
|
||||||
|
const handleHotkeySavedMessages = useCallback((e: KeyboardEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
openChat({ id: currentUserId });
|
||||||
|
}, [currentUserId, openChat]);
|
||||||
|
|
||||||
|
useHotkeys([
|
||||||
|
['mod+shift+F', handleHotkeySearch],
|
||||||
|
['mod+shift+S', handleHotkeySavedMessages],
|
||||||
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
clearTwoFaError();
|
clearTwoFaError();
|
||||||
|
|
||||||
@ -386,9 +389,16 @@ export default memo(withGlobal(
|
|||||||
},
|
},
|
||||||
shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations,
|
||||||
leftColumnWidth,
|
leftColumnWidth,
|
||||||
|
currentUserId,
|
||||||
} = global;
|
} = global;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
searchQuery: query, searchDate: date, activeChatFolder, shouldSkipHistoryAnimations, leftColumnWidth,
|
searchQuery: query,
|
||||||
|
searchDate: date,
|
||||||
|
activeChatFolder,
|
||||||
|
shouldSkipHistoryAnimations,
|
||||||
|
leftColumnWidth,
|
||||||
|
currentUserId,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(LeftColumn));
|
)(LeftColumn));
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import usePrevious from '../../../hooks/usePrevious';
|
|||||||
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||||
import { useFolderManagerForOrderedIds } from '../../../hooks/useFolderManager';
|
import { useFolderManagerForOrderedIds } from '../../../hooks/useFolderManager';
|
||||||
import { useChatAnimationType } from './hooks';
|
import { useChatAnimationType } from './hooks';
|
||||||
|
import { HotkeyItem, useHotkeys } from '../../../hooks/useHotkeys';
|
||||||
|
|
||||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||||
import Loading from '../../ui/Loading';
|
import Loading from '../../ui/Loading';
|
||||||
@ -74,14 +75,29 @@ const ChatList: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const [viewportIds, getMore] = useInfiniteScroll(undefined, orderedIds, undefined, CHAT_LIST_SLICE);
|
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(() => {
|
useEffect(() => {
|
||||||
if (!isActive || !orderedIds) {
|
if (!isActive || !orderedIds || !IS_PWA) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyDown(e: KeyboardEvent) {
|
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)/) || [];
|
const [, digit] = e.code.match(/Digit(\d)/) || [];
|
||||||
if (!digit) return;
|
if (!digit) return;
|
||||||
|
|
||||||
@ -90,14 +106,6 @@ const ChatList: FC<OwnProps> = ({
|
|||||||
|
|
||||||
openChat({ id: orderedIds![position], shouldReplaceHistory: true });
|
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);
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
|
|||||||
@ -96,7 +96,6 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
openChat,
|
openChat,
|
||||||
openTipsChat,
|
|
||||||
setGlobalSearchDate,
|
setGlobalSearchDate,
|
||||||
setSettingOption,
|
setSettingOption,
|
||||||
setGlobalSearchChatId,
|
setGlobalSearchChatId,
|
||||||
@ -196,8 +195,8 @@ const LeftMainHeader: FC<OwnProps & StateProps> = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleOpenTipsChat = useCallback(() => {
|
const handleOpenTipsChat = useCallback(() => {
|
||||||
openTipsChat({ langCode: lang.code });
|
openChatByUsername({ username: lang('Settings.TipsUsername') });
|
||||||
}, [lang.code, openTipsChat]);
|
}, [lang, openChatByUsername]);
|
||||||
|
|
||||||
const isSearchFocused = (
|
const isSearchFocused = (
|
||||||
Boolean(globalSearchChatId)
|
Boolean(globalSearchChatId)
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import React, {
|
|||||||
useRef,
|
useRef,
|
||||||
useCallback,
|
useCallback,
|
||||||
useState,
|
useState,
|
||||||
useEffect,
|
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
@ -13,9 +12,8 @@ import { MAIN_THREAD_ID } from '../../api/types';
|
|||||||
import { IAnchorPosition, ManagementScreens } from '../../types';
|
import { IAnchorPosition, ManagementScreens } from '../../types';
|
||||||
|
|
||||||
import {
|
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';
|
} from '../../util/environment';
|
||||||
import getKeyFromEvent from '../../util/getKeyFromEvent';
|
|
||||||
import {
|
import {
|
||||||
isChatBasicGroup, isChatChannel, isChatSuperGroup, isUserId,
|
isChatBasicGroup, isChatChannel, isChatSuperGroup, isUserId,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
@ -29,6 +27,7 @@ import {
|
|||||||
selectIsRightColumnShown,
|
selectIsRightColumnShown,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
|
import { useHotkeys } from '../../hooks/useHotkeys';
|
||||||
|
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
import HeaderMenuContainer from './HeaderMenuContainer.async';
|
import HeaderMenuContainer from './HeaderMenuContainer.async';
|
||||||
@ -143,27 +142,19 @@ const HeaderActions: FC<OwnProps & StateProps> = ({
|
|||||||
requestCall({ userId: chatId });
|
requestCall({ userId: chatId });
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
const handleHotkeySearchClick = useCallback((e: KeyboardEvent) => {
|
||||||
if (!canSearch) {
|
if (!canSearch || !IS_PWA || e.shiftKey) {
|
||||||
return undefined;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyDown(e: KeyboardEvent) {
|
e.preventDefault();
|
||||||
if (
|
handleSearchClick();
|
||||||
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);
|
|
||||||
};
|
|
||||||
}, [canSearch, handleSearchClick]);
|
}, [canSearch, handleSearchClick]);
|
||||||
|
|
||||||
|
useHotkeys([
|
||||||
|
['meta+F', handleHotkeySearchClick],
|
||||||
|
]);
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -253,6 +253,7 @@
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
.info {
|
.info {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -1149,19 +1149,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
{formatVoiceRecordDuration(currentRecordTime - startRecordTimeRef.current!)}
|
{formatVoiceRecordDuration(currentRecordTime - startRecordTimeRef.current!)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<StickerTooltip
|
|
||||||
chatId={chatId}
|
|
||||||
threadId={threadId}
|
|
||||||
isOpen={isStickerTooltipOpen}
|
|
||||||
onStickerSelect={handleStickerSelect}
|
|
||||||
/>
|
|
||||||
<EmojiTooltip
|
|
||||||
isOpen={isEmojiTooltipOpen}
|
|
||||||
emojis={filteredEmojis}
|
|
||||||
onClose={closeEmojiTooltip}
|
|
||||||
onEmojiSelect={insertEmoji}
|
|
||||||
addRecentEmoji={addRecentEmoji}
|
|
||||||
/>
|
|
||||||
<AttachMenu
|
<AttachMenu
|
||||||
chatId={chatId}
|
chatId={chatId}
|
||||||
isButtonVisible={!activeVoiceRecording && !editingMessage}
|
isButtonVisible={!activeVoiceRecording && !editingMessage}
|
||||||
@ -1188,6 +1175,19 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
onClose={closeBotCommandMenu}
|
onClose={closeBotCommandMenu}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<StickerTooltip
|
||||||
|
chatId={chatId}
|
||||||
|
threadId={threadId}
|
||||||
|
isOpen={isStickerTooltipOpen}
|
||||||
|
onStickerSelect={handleStickerSelect}
|
||||||
|
/>
|
||||||
|
<EmojiTooltip
|
||||||
|
isOpen={isEmojiTooltipOpen}
|
||||||
|
emojis={filteredEmojis}
|
||||||
|
onClose={closeEmojiTooltip}
|
||||||
|
onEmojiSelect={insertEmoji}
|
||||||
|
addRecentEmoji={addRecentEmoji}
|
||||||
|
/>
|
||||||
<SymbolMenu
|
<SymbolMenu
|
||||||
chatId={chatId}
|
chatId={chatId}
|
||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
|
|||||||
@ -1,24 +1,16 @@
|
|||||||
import { useEffect } from '../../../lib/teact/teact';
|
import { useHotkeys } from '../../../hooks/useHotkeys';
|
||||||
import { IS_MAC_OS } from '../../../util/environment';
|
|
||||||
import getKeyFromEvent from '../../../util/getKeyFromEvent';
|
|
||||||
|
|
||||||
const useCopySelectedMessages = (isActive: boolean, copySelectedMessages: NoneToVoidFunction) => {
|
const useCopySelectedMessages = (isActive: boolean, copySelectedMessages: NoneToVoidFunction) => {
|
||||||
useEffect(() => {
|
function handleCopy(e: KeyboardEvent) {
|
||||||
function handleCopy(e: KeyboardEvent) {
|
if (!isActive) {
|
||||||
if (((IS_MAC_OS && e.metaKey) || (!IS_MAC_OS && e.ctrlKey)) && getKeyFromEvent(e) === 'c') {
|
return;
|
||||||
e.preventDefault();
|
|
||||||
copySelectedMessages();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isActive) {
|
e.preventDefault();
|
||||||
document.addEventListener('keydown', handleCopy, false);
|
copySelectedMessages();
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
useHotkeys([['meta+C', handleCopy]]);
|
||||||
document.removeEventListener('keydown', handleCopy, false);
|
|
||||||
};
|
|
||||||
}, [copySelectedMessages, isActive]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useCopySelectedMessages;
|
export default useCopySelectedMessages;
|
||||||
|
|||||||
@ -6,9 +6,12 @@
|
|||||||
|
|
||||||
.description {
|
.description {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
|
||||||
&.has-image {
|
&.has-image {
|
||||||
margin: 1rem -0.5rem -0.375rem;
|
.content-inner:not(.forwarded-message) & {
|
||||||
|
margin: 0.5rem -0.5rem -0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
.invoice-image {
|
.invoice-image {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -16,6 +19,11 @@
|
|||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
border-bottom-left-radius: var(--border-bottom-left-radius);
|
border-bottom-left-radius: var(--border-bottom-left-radius);
|
||||||
border-bottom-right-radius: var(--border-bottom-right-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 {
|
.description-text {
|
||||||
|
|||||||
@ -72,7 +72,7 @@ const Invoice: FC<OwnProps> = ({
|
|||||||
<p className="title">{renderText(title)}</p>
|
<p className="title">{renderText(title)}</p>
|
||||||
)}
|
)}
|
||||||
{text && (
|
{text && (
|
||||||
<p>{renderText(text, ['emoji', 'br'])}</p>
|
<div>{renderText(text, ['emoji', 'br'])}</div>
|
||||||
)}
|
)}
|
||||||
<div className={`description ${photoUrl ? 'has-image' : ''}`}>
|
<div className={`description ${photoUrl ? 'has-image' : ''}`}>
|
||||||
{photoUrl && (
|
{photoUrl && (
|
||||||
|
|||||||
@ -163,7 +163,10 @@
|
|||||||
|
|
||||||
&__filter {
|
&__filter {
|
||||||
padding: 0 1rem 0.25rem 0.75rem;
|
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;
|
display: flex;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
|||||||
@ -165,9 +165,9 @@ const ListItem: FC<OwnProps> = ({
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={buildClassName('ListItem-button', isTouched && 'active')}
|
className={buildClassName('ListItem-button', isTouched && 'active')}
|
||||||
role="button"
|
role={!isStatic ? 'button' : undefined}
|
||||||
ref={buttonRef}
|
ref={buttonRef}
|
||||||
tabIndex={0}
|
tabIndex={!isStatic ? 0 : undefined}
|
||||||
onClick={(!inactive && IS_TOUCH_ENV) ? handleClick : undefined}
|
onClick={(!inactive && IS_TOUCH_ENV) ? handleClick : undefined}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
onContextMenu={(!inactive && contextActions) ? handleContextMenu : undefined}
|
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_CODE = 'en';
|
||||||
export const DEFAULT_LANG_PACK = 'android';
|
export const DEFAULT_LANG_PACK = 'android';
|
||||||
export const LANG_PACKS = ['android', 'ios', 'tdesktop', 'macos'] as const;
|
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 FEEDBACK_URL = 'https://bugs.telegram.org/?tag_ids=41&sort=time';
|
||||||
export const LIGHT_THEME_BG_COLOR = '#A2AF8E';
|
export const LIGHT_THEME_BG_COLOR = '#A2AF8E';
|
||||||
export const DARK_THEME_BG_COLOR = '#0F0F0F';
|
export const DARK_THEME_BG_COLOR = '#0F0F0F';
|
||||||
|
|||||||
@ -12,8 +12,6 @@ import {
|
|||||||
ARCHIVED_FOLDER_ID,
|
ARCHIVED_FOLDER_ID,
|
||||||
TOP_CHAT_MESSAGES_PRELOAD_LIMIT,
|
TOP_CHAT_MESSAGES_PRELOAD_LIMIT,
|
||||||
CHAT_LIST_LOAD_SLICE,
|
CHAT_LIST_LOAD_SLICE,
|
||||||
TIPS_USERNAME,
|
|
||||||
LOCALIZED_TIPS,
|
|
||||||
RE_TG_LINK,
|
RE_TG_LINK,
|
||||||
SERVICE_NOTIFICATIONS_USER_ID,
|
SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
TMP_CHAT_ID, ALL_FOLDER_ID, DEBUG,
|
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) => {
|
addActionHandler('loadAllChats', async (global, actions, payload) => {
|
||||||
const listType = payload.listType as 'active' | 'archived';
|
const listType = payload.listType as 'active' | 'archived';
|
||||||
const { onReplace } = payload;
|
const { onReplace } = payload;
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { orderBy } from '../../util/iteratees';
|
|||||||
import { LangFn } from '../../hooks/useLang';
|
import { LangFn } from '../../hooks/useLang';
|
||||||
import { getServerTime } from '../../util/serverTime';
|
import { getServerTime } from '../../util/serverTime';
|
||||||
import { prepareSearchWordsForNeedle } from '../../util/searchWords';
|
import { prepareSearchWordsForNeedle } from '../../util/searchWords';
|
||||||
|
import { formatPhoneNumber } from '../../util/phoneNumber';
|
||||||
|
|
||||||
const USER_COLOR_KEYS = [1, 8, 5, 2, 7, 4, 6];
|
const USER_COLOR_KEYS = [1, 8, 5, 2, 7, 4, 6];
|
||||||
|
|
||||||
@ -54,6 +55,10 @@ export function getUserFullName(user?: ApiUser) {
|
|||||||
return user.lastName;
|
return user.lastName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user.phoneNumber) {
|
||||||
|
return `+${formatPhoneNumber(user.phoneNumber)}`;
|
||||||
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -818,7 +818,7 @@ export type NonTypedActionNames = (
|
|||||||
'setAuthRememberMe' | 'clearAuthError' | 'uploadProfilePhoto' | 'goToAuthQrCode' | 'clearCache' |
|
'setAuthRememberMe' | 'clearAuthError' | 'uploadProfilePhoto' | 'goToAuthQrCode' | 'clearCache' |
|
||||||
// chats
|
// chats
|
||||||
'preloadTopChatMessages' | 'loadAllChats' | 'openChatWithInfo' | 'openLinkedChat' |
|
'preloadTopChatMessages' | 'loadAllChats' | 'openChatWithInfo' | 'openLinkedChat' |
|
||||||
'openSupportChat' | 'openTipsChat' | 'focusMessageInComments' | 'openChatByPhoneNumber' |
|
'openSupportChat' | 'focusMessageInComments' | 'openChatByPhoneNumber' |
|
||||||
'loadChatSettings' | 'loadFullChat' | 'loadTopChats' | 'requestChatUpdate' | 'updateChatMutedState' |
|
'loadChatSettings' | 'loadFullChat' | 'loadTopChats' | 'requestChatUpdate' | 'updateChatMutedState' |
|
||||||
'joinChannel' | 'leaveChannel' | 'deleteChannel' | 'toggleChatPinned' | 'toggleChatArchived' | 'toggleChatUnread' |
|
'joinChannel' | 'leaveChannel' | 'deleteChannel' | 'toggleChatPinned' | 'toggleChatArchived' | 'toggleChatUnread' |
|
||||||
'loadChatFolders' | 'loadRecommendedChatFolders' | 'editChatFolder' | 'addChatFolder' | 'deleteChatFolder' |
|
'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 { useHotkeys } from './useHotkeys';
|
||||||
import { IS_MAC_OS } from '../util/environment';
|
|
||||||
import getKeyFromEvent from '../util/getKeyFromEvent';
|
|
||||||
import getMessageIdsForSelectedText from '../util/getMessageIdsForSelectedText';
|
import getMessageIdsForSelectedText from '../util/getMessageIdsForSelectedText';
|
||||||
|
|
||||||
const useNativeCopySelectedMessages = (copyMessagesByIds: ({ messageIds }: { messageIds?: number[] }) => void) => {
|
const useNativeCopySelectedMessages = (copyMessagesByIds: ({ messageIds }: { messageIds?: number[] }) => void) => {
|
||||||
useEffect(() => {
|
function handleCopy(e: KeyboardEvent) {
|
||||||
function handleCopy(e: KeyboardEvent) {
|
const messageIds = getMessageIdsForSelectedText();
|
||||||
if (((IS_MAC_OS && e.metaKey) || (!IS_MAC_OS && e.ctrlKey)) && getKeyFromEvent(e) === 'c') {
|
|
||||||
const messageIds = getMessageIdsForSelectedText();
|
|
||||||
|
|
||||||
if (messageIds && messageIds.length > 0) {
|
if (messageIds && messageIds.length > 0) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
copyMessagesByIds({ messageIds });
|
copyMessagesByIds({ messageIds });
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('keydown', handleCopy, false);
|
useHotkeys([['meta+C', handleCopy]]);
|
||||||
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('keydown', handleCopy, false);
|
|
||||||
};
|
|
||||||
}, [copyMessagesByIds]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useNativeCopySelectedMessages;
|
export default useNativeCopySelectedMessages;
|
||||||
|
|||||||
@ -1849,4 +1849,8 @@ export default {
|
|||||||
key: 'ChannelVisibility.Forwarding.Disabled',
|
key: 'ChannelVisibility.Forwarding.Disabled',
|
||||||
value: 'Restrict Forwarding',
|
value: 'Restrict Forwarding',
|
||||||
},
|
},
|
||||||
|
'Settings.TipsUsername': {
|
||||||
|
key: 'Settings.TipsUsername',
|
||||||
|
value: 'TelegramTips',
|
||||||
|
},
|
||||||
} as ApiLangPack;
|
} 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…
x
Reference in New Issue
Block a user