More RTL support (#1071)

This commit is contained in:
Alexander Zinchuk 2021-06-12 17:20:19 +03:00
parent e54b877cf9
commit 45a3064153
142 changed files with 1075 additions and 263 deletions

View File

@ -319,29 +319,29 @@ export function buildChatTypingStatus(
if (update.action instanceof GramJs.SendMessageCancelAction) { if (update.action instanceof GramJs.SendMessageCancelAction) {
return undefined; return undefined;
} else if (update.action instanceof GramJs.SendMessageTypingAction) { } else if (update.action instanceof GramJs.SendMessageTypingAction) {
action = 'typing'; action = 'lng_user_typing';
} else if (update.action instanceof GramJs.SendMessageRecordVideoAction) { } else if (update.action instanceof GramJs.SendMessageRecordVideoAction) {
action = 'recording a video'; action = 'lng_send_action_record_video';
} else if (update.action instanceof GramJs.SendMessageUploadVideoAction) { } else if (update.action instanceof GramJs.SendMessageUploadVideoAction) {
action = 'uploading a video'; action = 'lng_send_action_upload_video';
} else if (update.action instanceof GramJs.SendMessageRecordAudioAction) { } else if (update.action instanceof GramJs.SendMessageRecordAudioAction) {
action = 'recording a voice message'; action = 'lng_send_action_record_audio';
} else if (update.action instanceof GramJs.SendMessageUploadAudioAction) { } else if (update.action instanceof GramJs.SendMessageUploadAudioAction) {
action = 'uploading a voice message'; action = 'lng_send_action_upload_audio';
} else if (update.action instanceof GramJs.SendMessageUploadPhotoAction) { } else if (update.action instanceof GramJs.SendMessageUploadPhotoAction) {
action = 'uploading a photo'; action = 'lng_send_action_upload_photo';
} else if (update.action instanceof GramJs.SendMessageUploadDocumentAction) { } else if (update.action instanceof GramJs.SendMessageUploadDocumentAction) {
action = 'uploading a file'; action = 'lng_send_action_upload_file';
} else if (update.action instanceof GramJs.SendMessageGeoLocationAction) { } else if (update.action instanceof GramJs.SendMessageGeoLocationAction) {
action = 'selecting a location to share'; action = 'selecting a location to share';
} else if (update.action instanceof GramJs.SendMessageChooseContactAction) { } else if (update.action instanceof GramJs.SendMessageChooseContactAction) {
action = 'selecting a contact to share'; action = 'selecting a contact to share';
} else if (update.action instanceof GramJs.SendMessageGamePlayAction) { } else if (update.action instanceof GramJs.SendMessageGamePlayAction) {
action = 'playing a game'; action = 'lng_playing_game';
} else if (update.action instanceof GramJs.SendMessageRecordRoundAction) { } else if (update.action instanceof GramJs.SendMessageRecordRoundAction) {
action = 'recording a round video'; action = 'lng_send_action_record_round';
} else if (update.action instanceof GramJs.SendMessageUploadRoundAction) { } else if (update.action instanceof GramJs.SendMessageUploadRoundAction) {
action = 'uploading a round video'; action = 'lng_send_action_upload_round';
} }
return { return {

View File

@ -232,10 +232,34 @@
top: 0.1875rem; top: 0.1875rem;
left: 0.1875rem; left: 0.1875rem;
} }
&[dir=rtl] {
.media-loading {
left: auto !important;
right: 0;
}
}
} }
.ProgressSpinner.size-s svg { .ProgressSpinner.size-s svg {
width: 2.25rem; width: 2.25rem;
height: 2.25rem; height: 2.25rem;
} }
&[dir=rtl] {
.toggle-play {
margin-left: .5rem;
margin-right: 0;
&.smaller {
margin-left: .75rem;
margin-right: 0;
}
}
.content,
.duration {
text-align: right;
}
}
} }

View File

@ -86,6 +86,7 @@ const Audio: FC<OwnProps & StateProps> = ({
const { content: { audio, voice }, isMediaUnread } = message; const { content: { audio, voice }, isMediaUnread } = message;
const isVoice = Boolean(voice); const isVoice = Boolean(voice);
const isSeeking = useRef<boolean>(false); const isSeeking = useRef<boolean>(false);
const lang = useLang();
const [isActivated, setIsActivated] = useState(false); const [isActivated, setIsActivated] = useState(false);
const shouldDownload = (isActivated || PRELOAD) && lastSyncTime; const shouldDownload = (isActivated || PRELOAD) && lastSyncTime;
@ -177,8 +178,6 @@ const Audio: FC<OwnProps & StateProps> = ({
onDateClick!(message.id, message.chatId); onDateClick!(message.id, message.chatId);
}, [onDateClick, message.id, message.chatId]); }, [onDateClick, message.id, message.chatId]);
const lang = useLang();
function getFirstLine() { function getFirstLine() {
if (isVoice) { if (isVoice) {
return senderTitle || 'Voice'; return senderTitle || 'Voice';
@ -264,7 +263,7 @@ const Audio: FC<OwnProps & StateProps> = ({
} }
return ( return (
<div className={fullClassName}> <div className={fullClassName} dir={lang.isRtl ? 'rtl' : undefined}>
{isSelectable && ( {isSelectable && (
<div className="message-select-control"> <div className="message-select-control">
{isSelected && <i className="icon-select" />} {isSelected && <i className="icon-select" />}
@ -277,6 +276,7 @@ const Audio: FC<OwnProps & StateProps> = ({
className={buttonClassNames.join(' ')} className={buttonClassNames.join(' ')}
ariaLabel={isPlaying ? 'Pause audio' : 'Play audio'} ariaLabel={isPlaying ? 'Pause audio' : 'Play audio'}
onClick={handleButtonClick} onClick={handleButtonClick}
isRtl={lang.isRtl}
> >
<i className="icon-play" /> <i className="icon-play" />
<i className="icon-pause" /> <i className="icon-pause" />

View File

@ -90,7 +90,7 @@ const DeleteChatModal: FC<OwnProps & StateProps & DispatchProps> = ({
function renderHeader() { function renderHeader() {
return ( return (
<div className="modal-header"> <div className="modal-header" dir={lang.isRtl ? 'rtl' : undefined}>
<Avatar <Avatar
size="tiny" size="tiny"
chat={chat} chat={chat}

View File

@ -9,6 +9,7 @@
position: relative; position: relative;
overflow: hidden; overflow: hidden;
cursor: pointer; cursor: pointer;
direction: ltr;
body.animation-level-1 & { body.animation-level-1 & {
.ripple-container { .ripple-container {
@ -30,7 +31,11 @@
} }
img:not(.emoji) { img:not(.emoji) {
margin-left: .5rem; margin-inline-start: .5rem;
}
&:dir(rtl) {
padding: 0.5rem;
} }
} }
@ -56,13 +61,17 @@
.message-text { .message-text {
overflow: hidden; overflow: hidden;
margin-left: 0.5rem; margin-inline-start: 0.5rem;
display: flex;
flex-direction: column-reverse;
.message-title { .message-title {
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
margin-bottom: 0.125rem; margin-bottom: 0.125rem;
flex: 1;
display: block;
} }
p { p {
@ -71,6 +80,7 @@
text-overflow: ellipsis; text-overflow: ellipsis;
height: 1.125rem; height: 1.125rem;
margin-bottom: 0; margin-bottom: 0;
flex: 1;
&::after { &::after {
content: none; content: none;
@ -104,7 +114,7 @@
} }
&.inside-input { &.inside-input {
padding-left: 0.5625rem; padding-inline-start: 0.5625rem;
margin: 0 0 -.125rem -0.1875rem; margin: 0 0 -.125rem -0.1875rem;
display: grid; display: grid;
grid-template-columns: auto 1fr; grid-template-columns: auto 1fr;
@ -122,7 +132,7 @@
} }
.message-text { .message-text {
margin-left: .375rem; margin-inline-start: .375rem;
} }
.message-title { .message-title {

View File

@ -63,7 +63,6 @@ const EmbeddedMessage: FC<OwnProps> = ({
> >
{mediaThumbnail && renderPictogram(pictogramId, mediaThumbnail, mediaBlobUrl, isRoundVideo)} {mediaThumbnail && renderPictogram(pictogramId, mediaThumbnail, mediaBlobUrl, isRoundVideo)}
<div className="message-text"> <div className="message-text">
<div className="message-title" dir="auto">{renderText(senderTitle || title || NBSP)}</div>
<p dir="auto"> <p dir="auto">
{!message ? ( {!message ? (
customText || NBSP customText || NBSP
@ -73,6 +72,7 @@ const EmbeddedMessage: FC<OwnProps> = ({
renderText(getMessageSummaryText(lang, message, Boolean(mediaThumbnail))) renderText(getMessageSummaryText(lang, message, Boolean(mediaThumbnail)))
)} )}
</p> </p>
<div className="message-title" dir="auto">{renderText(senderTitle || title || NBSP)}</div>
</div> </div>
</div> </div>
); );

View File

@ -190,7 +190,15 @@
} }
} }
&:dir(rtl) { &:dir(rtl),
&[dir=rtl] {
.file-progress,
.file-icon,
.file-preview {
margin-left: .75rem;
margin-right: 0;
}
.file-info { .file-info {
text-align: right; text-align: right;

View File

@ -82,7 +82,7 @@ const File: FC<OwnProps> = ({
); );
return ( return (
<div ref={elementRef} className={fullClassName}> <div ref={elementRef} className={fullClassName} dir={lang.isRtl ? 'rtl' : undefined}>
{isSelectable && ( {isSelectable && (
<div className="message-select-control"> <div className="message-select-control">
{isSelected && <i className="icon-select" />} {isSelected && <i className="icon-select" />}

View File

@ -31,6 +31,7 @@ type OwnProps = {
withFullInfo?: boolean; withFullInfo?: boolean;
withUpdatingStatus?: boolean; withUpdatingStatus?: boolean;
withChatType?: boolean; withChatType?: boolean;
noRtl?: boolean;
}; };
type StateProps = { type StateProps = {
@ -49,6 +50,7 @@ const GroupChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
withFullInfo, withFullInfo,
withUpdatingStatus, withUpdatingStatus,
withChatType, withChatType,
noRtl,
chat, chat,
onlineCount, onlineCount,
areMessagesLoaded, areMessagesLoaded,
@ -116,7 +118,7 @@ const GroupChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
} }
return ( return (
<div className="ChatInfo"> <div className="ChatInfo" dir={!noRtl && lang.isRtl ? 'rtl' : undefined}>
<Avatar <Avatar
key={chat.id} key={chat.id}
size={avatarSize} size={avatarSize}

View File

@ -6,6 +6,7 @@ import React, {
import { MIN_PASSWORD_LENGTH } from '../../config'; import { MIN_PASSWORD_LENGTH } from '../../config';
import { IS_TOUCH_ENV, IS_MOBILE_SCREEN } from '../../util/environment'; import { IS_TOUCH_ENV, IS_MOBILE_SCREEN } from '../../util/environment';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import useLang from '../../hooks/useLang';
import Button from '../ui/Button'; import Button from '../ui/Button';
@ -38,6 +39,7 @@ const PasswordForm: FC<OwnProps> = ({
}) => { }) => {
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const inputRef = useRef<HTMLInputElement>(null); const inputRef = useRef<HTMLInputElement>(null);
const lang = useLang();
const [password, setPassword] = useState(''); const [password, setPassword] = useState('');
const [canSubmit, setCanSubmit] = useState(false); const [canSubmit, setCanSubmit] = useState(false);
@ -90,7 +92,10 @@ const PasswordForm: FC<OwnProps> = ({
return ( return (
<form action="" onSubmit={handleSubmit} autoComplete="off"> <form action="" onSubmit={handleSubmit} autoComplete="off">
<div className={buildClassName('input-group password-input', password && 'touched', error && 'error')}> <div
className={buildClassName('input-group password-input', password && 'touched', error && 'error')}
dir={lang.isRtl ? 'rtl' : undefined}
>
<input <input
ref={inputRef} ref={inputRef}
className="form-control" className="form-control"
@ -99,6 +104,7 @@ const PasswordForm: FC<OwnProps> = ({
value={password || ''} value={password || ''}
autoComplete="current-password" autoComplete="current-password"
onChange={onPasswordChange} onChange={onPasswordChange}
dir="auto"
/> />
<label>{error || hint || placeholder}</label> <label>{error || hint || placeholder}</label>
<div <div

View File

@ -83,7 +83,7 @@ const Picker: FC<OwnProps> = ({
return ( return (
<div className="Picker"> <div className="Picker">
<div className="picker-header custom-scroll"> <div className="picker-header custom-scroll" dir={lang.isRtl ? 'rtl' : undefined}>
{selectedIds.map((id, i) => ( {selectedIds.map((id, i) => (
<PickerSelectedItem <PickerSelectedItem
chatOrUserId={id} chatOrUserId={id}

View File

@ -117,4 +117,29 @@
opacity: 0; opacity: 0;
transition: opacity .15s ease; transition: opacity .15s ease;
} }
&[dir=rtl] {
padding-left: 1rem;
padding-right: 0;
&.minimized {
padding-right: 0;
padding-left: 0;
}
.SearchInput & {
left: auto;
right: -.125rem;
}
.item-name {
margin-left: 0;
margin-right: 0.5rem;
}
.item-remove {
left: auto;
right: 0;
}
}
} }

View File

@ -82,6 +82,7 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
className={fullClassName} className={fullClassName}
onClick={() => onClick(clickArg)} onClick={() => onClick(clickArg)}
title={isMinimized ? titleText : undefined} title={isMinimized ? titleText : undefined}
dir={lang.isRtl ? 'rtl' : undefined}
> >
{iconElement} {iconElement}
{!isMinimized && ( {!isMinimized && (

View File

@ -29,6 +29,7 @@ type OwnProps = {
withFullInfo?: boolean; withFullInfo?: boolean;
withUpdatingStatus?: boolean; withUpdatingStatus?: boolean;
noStatusOrTyping?: boolean; noStatusOrTyping?: boolean;
noRtl?: boolean;
}; };
type StateProps = { type StateProps = {
@ -48,6 +49,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
withFullInfo, withFullInfo,
withUpdatingStatus, withUpdatingStatus,
noStatusOrTyping, noStatusOrTyping,
noRtl,
user, user,
isSavedMessages, isSavedMessages,
areMessagesLoaded, areMessagesLoaded,
@ -110,7 +112,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
} }
return ( return (
<div className="ChatInfo"> <div className="ChatInfo" dir={!noRtl && lang.isRtl ? 'rtl' : undefined}>
<Avatar <Avatar
key={user.id} key={user.id}
size={avatarSize} size={avatarSize}

View File

@ -12,6 +12,7 @@ type OwnProps = {
text: string; text: string;
className?: string; className?: string;
children?: any; children?: any;
isRtl?: boolean;
}; };
type DispatchProps = Pick<GlobalActions, 'toggleSafeLinkModal' | 'openTelegramLink'>; type DispatchProps = Pick<GlobalActions, 'toggleSafeLinkModal' | 'openTelegramLink'>;
@ -21,6 +22,7 @@ const SafeLink: FC<OwnProps & DispatchProps> = ({
text, text,
className, className,
children, children,
isRtl,
toggleSafeLinkModal, toggleSafeLinkModal,
openTelegramLink, openTelegramLink,
}) => { }) => {
@ -65,6 +67,7 @@ const SafeLink: FC<OwnProps & DispatchProps> = ({
rel="noopener noreferrer" rel="noopener noreferrer"
className={classNames} className={classNames}
onClick={handleClick} onClick={handleClick}
dir={isRtl ? 'rtl' : 'auto'}
> >
{content} {content}
</a> </a>

View File

@ -4,7 +4,7 @@
.sender-name { .sender-name {
&::after { &::after {
content: '\00a0is\00a0'; content: '\00a0';
color: var(--color-text-secondary); color: var(--color-text-secondary);
} }
} }
@ -17,6 +17,11 @@
&::after { &::after {
content: '...'; content: '...';
animation: typing-animation 1s steps(4, start) infinite; animation: typing-animation 1s steps(4, start) infinite;
html[lang=ar] &,
html[lang=fa] & {
animation-name: typing-animation-rtl;
}
} }
} }
} }
@ -26,3 +31,9 @@
transform: translateX(-1rem); transform: translateX(-1rem);
} }
} }
@keyframes typing-animation-rtl {
from {
transform: translateX(1rem);
}
}

View File

@ -6,6 +6,7 @@ import { ApiUser, ApiTypingStatus } from '../../api/types';
import { selectUser } from '../../modules/selectors'; import { selectUser } from '../../modules/selectors';
import { getUserFirstOrLastName } from '../../modules/helpers'; import { getUserFirstOrLastName } from '../../modules/helpers';
import renderText from './helpers/renderText'; import renderText from './helpers/renderText';
import useLang from '../../hooks/useLang';
import './TypingStatus.scss'; import './TypingStatus.scss';
@ -18,14 +19,16 @@ type StateProps = {
}; };
const TypingStatus: FC<OwnProps & StateProps> = ({ typingStatus, typingUser }) => { const TypingStatus: FC<OwnProps & StateProps> = ({ typingStatus, typingUser }) => {
const lang = useLang();
const typingUserName = typingUser && !typingUser.isSelf && getUserFirstOrLastName(typingUser); const typingUserName = typingUser && !typingUser.isSelf && getUserFirstOrLastName(typingUser);
return ( return (
<p className="typing-status"> <p className="typing-status" dir={lang.isRtl ? 'rtl' : 'auto'}>
{typingUserName && ( {typingUserName && (
<span className="sender-name" dir="auto">{renderText(typingUserName)}</span> <span className="sender-name" dir="auto">{renderText(typingUserName)}</span>
)} )}
{typingStatus.action} {/* fix for translation "username _is_ typing" */}
{lang(typingStatus.action).replace('{user}', '').trim()}
<span className="ellipsis" /> <span className="ellipsis" />
</p> </p>
); );

View File

@ -20,6 +20,7 @@ const UnpinAllMessagesModal: FC<OwnProps> = ({
onUnpin, onUnpin,
}) => { }) => {
const lang = useLang(); const lang = useLang();
return ( return (
<Modal <Modal
isOpen={isOpen} isOpen={isOpen}

View File

@ -42,6 +42,7 @@
.site-description, .site-description,
.site-title { .site-title {
word-break: break-word; word-break: break-word;
text-align: initial;
} }
.site-name { .site-name {
@ -80,4 +81,18 @@
width: 1rem !important; width: 1rem !important;
height: 1rem !important; height: 1rem !important;
} }
&[dir=rtl] {
padding: .25rem 3.75rem 0 0;
.Media,
&.without-photo::before {
left: auto;
right: 0;
}
.content {
text-align: right;
}
}
} }

View File

@ -70,19 +70,25 @@ const WebLink: FC<OwnProps> = ({ message, senderTitle, onMessageClick }) => {
<div <div
className={className} className={className}
data-initial={(siteName || displayUrl)[0]} data-initial={(siteName || displayUrl)[0]}
dir={lang.isRtl ? 'rtl' : undefined}
> >
{photo && ( {photo && (
<Media message={message} /> <Media message={message} />
)} )}
<div className="content"> <div className="content">
<Link className="site-title" onClick={handleMessageClick}>{renderText(title || siteName || displayUrl)}</Link> <Link isRtl={lang.isRtl} className="site-title" onClick={handleMessageClick}>
{renderText(title || siteName || displayUrl)}
</Link>
{truncatedDescription && ( {truncatedDescription && (
<Link className="site-description" onClick={handleMessageClick}>{renderText(truncatedDescription)}</Link> <Link isRtl={lang.isRtl} className="site-description" onClick={handleMessageClick}>
{renderText(truncatedDescription)}
</Link>
)} )}
<SafeLink <SafeLink
url={url} url={url}
className="site-name" className="site-name"
text="" text=""
isRtl={lang.isRtl}
> >
{url.replace('mailto:', '') || displayUrl} {url.replace('mailto:', '') || displayUrl}
</SafeLink> </SafeLink>
@ -93,6 +99,7 @@ const WebLink: FC<OwnProps> = ({ message, senderTitle, onMessageClick }) => {
<Link <Link
className="date" className="date"
onClick={handleMessageClick} onClick={handleMessageClick}
isRtl={lang.isRtl}
> >
{formatPastTimeShort(lang, message.date * 1000)} {formatPastTimeShort(lang, message.date * 1000)}
</Link> </Link>

View File

@ -287,6 +287,7 @@ function processEntity(
<a <a
href={`tel:${entityText}`} href={`tel:${entityText}`}
className="text-entity-link" className="text-entity-link"
dir="auto"
> >
{renderMessagePart(renderedContent)} {renderMessagePart(renderedContent)}
</a> </a>

View File

@ -15,13 +15,13 @@
color: var(--color-text-lighter); color: var(--color-text-lighter);
font-weight: 500; font-weight: 500;
line-height: 2rem; line-height: 2rem;
margin-left: 1.9rem; margin-inline-start: 1.875rem;
white-space: nowrap; white-space: nowrap;
} }
@media (max-width: 950px) { @media (max-width: 950px) {
> .state-text { > .state-text {
margin-left: 1.2rem; margin-inline-start: 1.25rem;
} }
} }
} }

View File

@ -18,7 +18,7 @@ const ConnectionState: FC<StateProps> = ({ connectionState }) => {
const isConnecting = connectionState === 'connectionStateConnecting'; const isConnecting = connectionState === 'connectionStateConnecting';
return isConnecting && ( return isConnecting && (
<div id="ConnectionState"> <div id="ConnectionState" dir={lang.isRtl ? 'rtl' : undefined}>
<Spinner color="black" /> <Spinner color="black" />
<div className="state-text">{lang('WaitingForNetwork')}</div> <div className="state-text">{lang('WaitingForNetwork')}</div>
</div> </div>

View File

@ -94,11 +94,14 @@
} }
.last-message { .last-message {
.sender-name, .draft { .draft {
&::after { &::after {
content: ': '; content: ': ';
} }
} }
.colon {
margin-inline-end: .25rem;
}
.media-preview { .media-preview {
position: relative; position: relative;
@ -110,7 +113,7 @@
object-fit: cover; object-fit: cover;
border-radius: .125rem; border-radius: .125rem;
vertical-align: -.25rem; vertical-align: -.25rem;
margin-right: .25rem; margin-inline-end: .25rem;
&.round { &.round {
border-radius: .625rem; border-radius: .625rem;
@ -127,8 +130,34 @@
font-size: .75rem; font-size: .75rem;
color: #fff; color: #fff;
position: absolute; position: absolute;
left: .25rem;
top: .1875rem; top: .1875rem;
margin-inline-start: -1.25rem;
}
}
}
&[dir=rtl] {
.info {
.LastMessageMeta {
margin-left: 0;
margin-right: auto;
}
.title, .subtitle {
padding-left: .15rem;
padding-right: 0;
}
.icon-muted-chat {
margin-left: 0;
margin-right: 0.25rem;
}
.last-message, .typing-status {
padding-left: 0.5rem;
padding-right: 0;
text-align: right;
unicode-bidi: plaintext;
} }
} }
} }

View File

@ -228,7 +228,10 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
return ( return (
<p className="last-message" dir="auto"> <p className="last-message" dir="auto">
{senderName && ( {senderName && (
<span className="sender-name">{renderText(senderName)}</span> <>
<span className="sender-name">{renderText(senderName)}</span>
<span className="colon">:</span>
</>
)} )}
{renderMessageSummary(lang, lastMessage!, mediaBlobUrl || mediaThumbnail, isRoundVideo)} {renderMessageSummary(lang, lastMessage!, mediaBlobUrl || mediaThumbnail, isRoundVideo)}
</p> </p>

View File

@ -163,7 +163,7 @@ const ChatFolders: FC<StateProps & DispatchProps> = ({
) : undefined} ) : undefined}
<Transition <Transition
ref={transitionRef} ref={transitionRef}
name="slide" name={lang.isRtl ? 'slide-reversed' : 'slide'}
activeKey={activeTab} activeKey={activeTab}
renderCount={folderTabs ? folderTabs.length : undefined} renderCount={folderTabs ? folderTabs.length : undefined}
> >

View File

@ -57,6 +57,11 @@
flex-shrink: 0; flex-shrink: 0;
} }
[dir=rtl] .archived-badge {
margin-left: 0;
margin-right: auto;
}
.Menu .bubble { .Menu .bubble {
min-width: 17rem; min-width: 17rem;
} }

View File

@ -88,7 +88,9 @@ const AudioResults: FC<OwnProps & StateProps & DispatchProps> = ({
key={message.id} key={message.id}
> >
{shouldDrawDateDivider && ( {shouldDrawDateDivider && (
<p className="section-heading">{formatMonthAndYear(lang, new Date(message.date * 1000))}</p> <p className="section-heading" dir={lang.isRtl ? 'rtl' : undefined}>
{formatMonthAndYear(lang, new Date(message.date * 1000))}
</p>
)} )}
<Audio <Audio
key={message.id} key={message.id}

View File

@ -65,8 +65,8 @@
font-size: .75rem; font-size: .75rem;
color: #fff; color: #fff;
position: absolute; position: absolute;
left: .25rem;
top: .1875rem; top: .1875rem;
margin-inline-start: -1.25rem;
} }
} }
} }
@ -75,4 +75,10 @@
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
} }
&[dir=rtl] {
.subtitle {
text-align: right;
}
}
} }

View File

@ -205,7 +205,7 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
/> />
)} )}
{!!localResults.length && ( {!!localResults.length && (
<div className="chat-selection no-selection no-scrollbar"> <div className="chat-selection no-selection no-scrollbar" dir={lang.isRtl ? 'rtl' : undefined}>
{localResults.map((id) => ( {localResults.map((id) => (
<PickerSelectedItem <PickerSelectedItem
chatOrUserId={id} chatOrUserId={id}
@ -217,7 +217,7 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
)} )}
{!!localResults.length && ( {!!localResults.length && (
<div className="search-section"> <div className="search-section">
<h3 className="section-heading"> <h3 className="section-heading" dir={lang.isRtl ? 'auto' : undefined}>
{localResults.length > LESS_LIST_ITEMS_AMOUNT && ( {localResults.length > LESS_LIST_ITEMS_AMOUNT && (
<Link onClick={handleClickShowMoreLocal}> <Link onClick={handleClickShowMoreLocal}>
{lang(shouldShowMoreLocal ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')} {lang(shouldShowMoreLocal ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')}
@ -241,7 +241,7 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
)} )}
{!!globalResults.length && ( {!!globalResults.length && (
<div className="search-section"> <div className="search-section">
<h3 className="section-heading"> <h3 className="section-heading" dir={lang.isRtl ? 'auto' : undefined}>
{globalResults.length > LESS_LIST_ITEMS_AMOUNT && ( {globalResults.length > LESS_LIST_ITEMS_AMOUNT && (
<Link onClick={handleClickShowMoreGlobal}> <Link onClick={handleClickShowMoreGlobal}>
{lang(shouldShowMoreGlobal ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')} {lang(shouldShowMoreGlobal ? 'ChatList.Search.ShowLess' : 'ChatList.Search.ShowMore')}
@ -266,7 +266,7 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
)} )}
{!!foundMessages.length && ( {!!foundMessages.length && (
<div className="search-section"> <div className="search-section">
<h3 className="section-heading">{lang('SearchMessages')}</h3> <h3 className="section-heading" dir={lang.isRtl ? 'auto' : undefined}>{lang('SearchMessages')}</h3>
{foundMessages.map(renderFoundMessage)} {foundMessages.map(renderFoundMessage)}
</div> </div>
)} )}

View File

@ -41,6 +41,19 @@
left: .625rem; left: .625rem;
top: -1px; top: -1px;
} }
&[dir=rtl],
&[dir=auto] {
padding-left: 0;
padding-right: 1.25rem;
margin: 0 -1.25rem 0 1rem !important;
text-align: initial;
&::before {
left: auto;
right: .625rem;
}
}
} }
.LeftSearch .search-section .section-heading, .LeftSearch .search-section .section-heading,
@ -97,6 +110,7 @@
.handle { .handle {
unicode-bidi: plaintext; unicode-bidi: plaintext;
color: var(--color-primary); color: var(--color-primary);
unicode-bidi: plaintext;
&::before { &::before {
content: '@'; content: '@';
@ -115,6 +129,16 @@
} }
} }
} }
&[dir=rtl] {
.status {
text-align: right;
.handle {
float: right;
}
}
}
} }
} }
@ -156,6 +180,15 @@
color: var(--color-links-hover); color: var(--color-links-hover);
} }
} }
&[dir=rtl],
&[dir=auto] {
.Link {
float: left;
margin-left: 1rem;
margin-right: 0;
}
}
} }
.Loading { .Loading {
@ -186,9 +219,23 @@
margin-right: auto; margin-right: auto;
} }
} }
&[dir=rtl] {
> .PickerSelectedItem:last-child {
margin-left: auto;
margin-right: 0;
}
}
} }
.NothingFound { .NothingFound {
height: 100%; height: 100%;
} }
[dir=rtl] {
.message-date {
padding-left: 0;
padding-right: 1rem;
}
}
} }

View File

@ -8,6 +8,7 @@ import { GlobalSearchContent } from '../../../types';
import { pick } from '../../../util/iteratees'; import { pick } from '../../../util/iteratees';
import { parseDateString } from '../../../util/dateFormat'; import { parseDateString } from '../../../util/dateFormat';
import useLang from '../../../hooks/useLang';
import TabList from '../../ui/TabList'; import TabList from '../../ui/TabList';
import Transition from '../../ui/Transition'; import Transition from '../../ui/Transition';
@ -58,6 +59,7 @@ const LeftSearch: FC<OwnProps & StateProps & DispatchProps> = ({
setGlobalSearchDate, setGlobalSearchDate,
onReset, onReset,
}) => { }) => {
const lang = useLang();
const [activeTab, setActiveTab] = useState(0); const [activeTab, setActiveTab] = useState(0);
const dateSearchQuery = useMemo(() => parseDateString(searchQuery), [searchQuery]); const dateSearchQuery = useMemo(() => parseDateString(searchQuery), [searchQuery]);
@ -74,7 +76,11 @@ const LeftSearch: FC<OwnProps & StateProps & DispatchProps> = ({
return ( return (
<div className="LeftSearch"> <div className="LeftSearch">
<TabList activeTab={activeTab} tabs={chatId ? CHAT_TABS : TABS} onSwitchTab={handleSwitchTab} /> <TabList activeTab={activeTab} tabs={chatId ? CHAT_TABS : TABS} onSwitchTab={handleSwitchTab} />
<Transition name="slide" renderCount={TRANSITION_RENDER_COUNT} activeKey={currentContent}> <Transition
name={lang.isRtl ? 'slide-reversed' : 'slide'}
renderCount={TRANSITION_RENDER_COUNT}
activeKey={currentContent}
>
{() => { {() => {
switch (currentContent) { switch (currentContent) {
case GlobalSearchContent.ChatList: case GlobalSearchContent.ChatList:

View File

@ -78,10 +78,13 @@ const LinkResults: FC<OwnProps & StateProps & DispatchProps> = ({
return ( return (
<div <div
className="ListItem" className="ListItem"
dir={lang.isRtl ? 'rtl' : undefined}
key={message.id} key={message.id}
> >
{shouldDrawDateDivider && ( {shouldDrawDateDivider && (
<p className="section-heading">{formatMonthAndYear(lang, new Date(message.date * 1000))}</p> <p className="section-heading" dir={lang.isRtl ? 'rtl' : undefined}>
{formatMonthAndYear(lang, new Date(message.date * 1000))}
</p>
)} )}
<WebLink <WebLink
key={message.id} key={message.id}

View File

@ -41,6 +41,7 @@ const MediaResults: FC<OwnProps & StateProps & DispatchProps> = ({
openMediaViewer, openMediaViewer,
}) => { }) => {
const lang = useLang(); const lang = useLang();
const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => { const handleLoadMore = useCallback(({ direction }: { direction: LoadMoreDirection }) => {
if (lastSyncTime && direction === LoadMoreDirection.Backwards) { if (lastSyncTime && direction === LoadMoreDirection.Backwards) {
runThrottled(() => { runThrottled(() => {
@ -75,7 +76,7 @@ const MediaResults: FC<OwnProps & StateProps & DispatchProps> = ({
function renderGallery() { function renderGallery() {
return ( return (
<div className="media-list"> <div className="media-list" dir={lang.isRtl ? 'rtl' : undefined}>
{foundMessages.map((message) => ( {foundMessages.map((message) => (
<Media <Media
key={message.id} key={message.id}

View File

@ -64,7 +64,7 @@
width: 1rem; width: 1rem;
height: 1rem; height: 1rem;
background-size: 1rem; background-size: 1rem;
margin-right: 1px; margin-inline-end: 1px;
vertical-align: -3px; vertical-align: -3px;
} }
} }
@ -77,5 +77,12 @@
.Button { .Button {
margin-left: auto; margin-left: auto;
} }
&[dir=rtl] {
.Button {
margin-left: 0;
margin-right: auto;
}
}
} }
} }

View File

@ -74,10 +74,10 @@ const RecentContacts: FC<OwnProps & StateProps & DispatchProps> = ({
return ( return (
<div className="RecentContacts custom-scroll"> <div className="RecentContacts custom-scroll">
{topUserIds && ( {topUserIds && (
<div className="top-peers-section"> <div className="top-peers-section" dir={lang.isRtl ? 'rtl' : undefined}>
<div ref={topUsersRef} className="top-peers no-selection"> <div ref={topUsersRef} className="top-peers no-selection">
{topUserIds.map((userId) => ( {topUserIds.map((userId) => (
<div className="top-peer-item" onClick={() => handleClick(userId)}> <div className="top-peer-item" onClick={() => handleClick(userId)} dir={lang.isRtl ? 'rtl' : undefined}>
<Avatar user={usersById[userId]} /> <Avatar user={usersById[userId]} />
<div className="top-peer-name">{renderText(getUserFirstOrLastName(usersById[userId]) || NBSP)}</div> <div className="top-peer-name">{renderText(getUserFirstOrLastName(usersById[userId]) || NBSP)}</div>
</div> </div>
@ -87,7 +87,7 @@ const RecentContacts: FC<OwnProps & StateProps & DispatchProps> = ({
)} )}
{recentlyFoundChatIds && ( {recentlyFoundChatIds && (
<div className="search-section pt-1"> <div className="search-section pt-1">
<h3 className="section-heading mt-0 recent-chats-header"> <h3 className="section-heading mt-0 recent-chats-header" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('Recent')} {lang('Recent')}
<Button <Button
@ -96,6 +96,7 @@ const RecentContacts: FC<OwnProps & StateProps & DispatchProps> = ({
color="translucent" color="translucent"
ariaLabel="Clear recent chats" ariaLabel="Clear recent chats"
onClick={clearRecentlyFoundChats} onClick={clearRecentlyFoundChats}
isRtl={lang.isRtl}
> >
<i className="icon-close" /> <i className="icon-close" />
</Button> </Button>

View File

@ -129,6 +129,10 @@
color: var(--color-text-secondary); color: var(--color-text-secondary);
margin-bottom: 2rem; margin-bottom: 2rem;
position: relative; position: relative;
&[dir=rtl] {
text-align: right;
}
} }
&-description { &-description {
@ -140,6 +144,11 @@
.settings-content.two-fa & { .settings-content.two-fa & {
font-size: 1rem; font-size: 1rem;
} }
&[dir=rtl] {
text-align: right;
unicode-bidi: plaintext;
}
} }
&-description-larger { &-description-larger {
@ -148,6 +157,10 @@
color: var(--color-text-secondary); color: var(--color-text-secondary);
margin-top: 2rem; margin-top: 2rem;
margin-bottom: 0.75rem; margin-bottom: 0.75rem;
&[dir=rtl] {
text-align: right;
}
} }
.ListItem { .ListItem {
@ -238,6 +251,20 @@
color: var(--color-text-secondary); color: var(--color-text-secondary);
} }
} }
&[dir=rtl] {
.multiline-menu-item {
.title, .subtitle {
text-align: right;
}
.date {
float: left;
margin-left: 0;
margin-right: 1rem;
}
}
}
} }
.RangeSlider { .RangeSlider {

View File

@ -198,13 +198,13 @@ const SettingsEditProfile: FC<StateProps & DispatchProps> = ({
error={error === ERROR_BIO_TOO_LONG ? error : undefined} error={error === ERROR_BIO_TOO_LONG ? error : undefined}
/> />
<p className="settings-item-description"> <p className="settings-item-description" dir={lang.isRtl ? 'rtl' : undefined}>
{renderText(lang('lng_settings_about_bio'), ['br', 'simple_markdown'])} {renderText(lang('lng_settings_about_bio'), ['br', 'simple_markdown'])}
</p> </p>
</div> </div>
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header">{lang('Username')}</h4> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('Username')}</h4>
<UsernameInput <UsernameInput
currentUsername={username || ''} currentUsername={username || ''}
@ -214,11 +214,11 @@ const SettingsEditProfile: FC<StateProps & DispatchProps> = ({
onChange={handleUsernameChange} onChange={handleUsernameChange}
/> />
<p className="settings-item-description"> <p className="settings-item-description" dir={lang.isRtl ? 'rtl' : undefined}>
{renderText(lang('UsernameHelp'), ['br', 'simple_markdown'])} {renderText(lang('UsernameHelp'), ['br', 'simple_markdown'])}
</p> </p>
{username && ( {username && (
<p className="settings-item-description"> <p className="settings-item-description" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('lng_username_link')}<br /> {lang('lng_username_link')}<br />
<span className="username-link">https://t.me/{username}</span> <span className="username-link">https://t.me/{username}</span>
</p> </p>

View File

@ -109,7 +109,7 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
return ( return (
<div className="settings-content custom-scroll"> <div className="settings-content custom-scroll">
<div className="settings-item pt-3"> <div className="settings-item pt-3">
<h4 className="settings-item-header">{lang('SETTINGS')}</h4> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('SETTINGS')}</h4>
<RangeSlider <RangeSlider
label={lang('TextSize')} label={lang('TextSize')}
@ -128,10 +128,12 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
</div> </div>
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header"> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
Animation Level Animation Level
</h4> </h4>
<p className="settings-item-description">Choose the desired animations amount.</p> <p className="settings-item-description" dir={lang.isRtl ? 'rtl' : undefined}>
Choose the desired animations amount.
</p>
<RangeSlider <RangeSlider
options={ANIMATION_LEVEL_OPTIONS} options={ANIMATION_LEVEL_OPTIONS}
@ -142,7 +144,7 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
{KEYBOARD_SEND_OPTIONS && ( {KEYBOARD_SEND_OPTIONS && (
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header">{lang('Keyboard')}</h4> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('Keyboard')}</h4>
<RadioGroup <RadioGroup
name="keyboard-send-settings" name="keyboard-send-settings"
@ -154,7 +156,7 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
)} )}
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header">{lang('AutoDownloadMedia')}</h4> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('AutoDownloadMedia')}</h4>
<Checkbox <Checkbox
label={lang('Contacts')} label={lang('Contacts')}
@ -179,7 +181,7 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
</div> </div>
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header">{lang('AutoplayMedia')}</h4> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('AutoplayMedia')}</h4>
<Checkbox <Checkbox
label={lang('GifsTab2')} label={lang('GifsTab2')}
@ -194,7 +196,7 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
</div> </div>
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header">{lang('AccDescrStickers')}</h4> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('AccDescrStickers')}</h4>
<Checkbox <Checkbox
label={lang('SuggestStickers')} label={lang('SuggestStickers')}

View File

@ -47,6 +47,12 @@
.input-group { .input-group {
margin-bottom: 0; margin-bottom: 0;
&[dir=rtl] {
label {
transform: scale(0.75) translate(1.25rem, -2.25rem);
}
}
} }
.input-group:first-child { .input-group:first-child {

View File

@ -166,7 +166,9 @@ const SettingsHeader: FC<OwnProps & DispatchProps> = ({
trigger={SettingsMenuButton} trigger={SettingsMenuButton}
positionX="right" positionX="right"
> >
<MenuItem icon="delete" destructive onClick={openDeleteFolderConfirmation}>Delete Folder</MenuItem> <MenuItem icon="delete" destructive onClick={openDeleteFolderConfirmation}>
Delete Folder
</MenuItem>
</DropdownMenu> </DropdownMenu>
)} )}
</div> </div>

View File

@ -38,6 +38,7 @@ const SettingsLanguage: FC<StateProps & DispatchProps> = ({
setLanguage(langCode, () => { setLanguage(langCode, () => {
unmarkIsLoading(); unmarkIsLoading();
setSettingOption({ language: langCode }); setSettingOption({ language: langCode });
}); });
}, [markIsLoading, unmarkIsLoading, setSettingOption]); }, [markIsLoading, unmarkIsLoading, setSettingOption]);

View File

@ -52,7 +52,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
icon="settings" icon="settings"
onClick={() => onScreenSelect(SettingsScreens.General)} onClick={() => onScreenSelect(SettingsScreens.General)}
> >
{lang('GeneralSettings')} {lang('Telegram.GeneralSettingsViewController')}
</ListItem> </ListItem>
<ListItem <ListItem
icon="unmute" icon="unmute"

View File

@ -76,7 +76,9 @@ const SettingsNotifications: FC<StateProps & DispatchProps> = ({
return ( return (
<div className="settings-content custom-scroll"> <div className="settings-content custom-scroll">
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header">{lang('AutodownloadPrivateChats')}</h4> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('AutodownloadPrivateChats')}
</h4>
<Checkbox <Checkbox
label={lang('NotificationsForPrivateChats')} label={lang('NotificationsForPrivateChats')}
@ -93,7 +95,7 @@ const SettingsNotifications: FC<StateProps & DispatchProps> = ({
</div> </div>
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header">{lang('FilterGroups')}</h4> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('FilterGroups')}</h4>
<Checkbox <Checkbox
label={lang('NotificationsForGroups')} label={lang('NotificationsForGroups')}
@ -110,7 +112,7 @@ const SettingsNotifications: FC<StateProps & DispatchProps> = ({
</div> </div>
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header">{lang('FilterChannels')}</h4> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('FilterChannels')}</h4>
<Checkbox <Checkbox
label={lang('NotificationsForChannels')} label={lang('NotificationsForChannels')}
@ -127,7 +129,7 @@ const SettingsNotifications: FC<StateProps & DispatchProps> = ({
</div> </div>
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header">{lang('PhoneOther')}</h4> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{lang('PhoneOther')}</h4>
<Checkbox <Checkbox
label={lang('ContactJoined')} label={lang('ContactJoined')}

View File

@ -111,7 +111,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
</div> </div>
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header mb-4">{lang('PrivacyTitle')}</h4> <h4 className="settings-item-header mb-4" dir={lang.isRtl ? 'rtl' : undefined}>{lang('PrivacyTitle')}</h4>
<ListItem <ListItem
narrow narrow

View File

@ -55,11 +55,13 @@ const SettingsPrivacyActiveSessions: FC<StateProps & DispatchProps> = ({
function renderCurrentSession(session: ApiSession) { function renderCurrentSession(session: ApiSession) {
return ( return (
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header mb-4">{lang('AuthSessions.CurrentSession')}</h4> <h4 className="settings-item-header mb-4" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('AuthSessions.CurrentSession')}
</h4>
<ListItem narrow inactive> <ListItem narrow inactive>
<div className="multiline-menu-item"> <div className="multiline-menu-item" dir="auto">
<span className="title">{session.appName}</span> <span className="title" dir="auto">{session.appName}</span>
<span className="subtitle black tight">{getDeviceEnvironment(session)}</span> <span className="subtitle black tight">{getDeviceEnvironment(session)}</span>
<span className="subtitle">{session.ip} - {getLocation(session)}</span> <span className="subtitle">{session.ip} - {getLocation(session)}</span>
</div> </div>
@ -81,7 +83,7 @@ const SettingsPrivacyActiveSessions: FC<StateProps & DispatchProps> = ({
function renderOtherSessions(sessions: ApiSession[]) { function renderOtherSessions(sessions: ApiSession[]) {
return ( return (
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header mb-4">Other Sessions</h4> <h4 className="settings-item-header mb-4" dir={lang.isRtl ? 'rtl' : undefined}>Other Sessions</h4>
{sessions.map(renderSession)} {sessions.map(renderSession)}
</div> </div>
@ -102,7 +104,7 @@ const SettingsPrivacyActiveSessions: FC<StateProps & DispatchProps> = ({
}, },
}]} }]}
> >
<div className="multiline-menu-item full-size"> <div className="multiline-menu-item full-size" dir="auto">
<span className="date">{formatPastTimeShort(lang, session.dateActive * 1000)}</span> <span className="date">{formatPastTimeShort(lang, session.dateActive * 1000)}</span>
<span className="title">{session.appName}</span> <span className="title">{session.appName}</span>
<span className="subtitle black tight">{getDeviceEnvironment(session)}</span> <span className="subtitle black tight">{getDeviceEnvironment(session)}</span>
@ -141,7 +143,11 @@ function getDeviceEnvironment(session: ApiSession) {
} }
export default memo(withGlobal( export default memo(withGlobal(
(global): StateProps => ({ activeSessions: global.activeSessions }), (global): StateProps => {
return {
activeSessions: global.activeSessions,
};
},
(setGlobal, actions): DispatchProps => pick(actions, [ (setGlobal, actions): DispatchProps => pick(actions, [
'loadAuthorizations', 'terminateAuthorization', 'terminateAllAuthorizations', 'loadAuthorizations', 'terminateAuthorization', 'terminateAllAuthorizations',
]), ]),

View File

@ -84,7 +84,7 @@ const SettingsPrivacyBlockedUsers: FC<StateProps & DispatchProps> = ({
<div className="settings-fab-wrapper"> <div className="settings-fab-wrapper">
<div className="settings-content infinite-scroll"> <div className="settings-content infinite-scroll">
<div className="settings-item"> <div className="settings-item">
<p className="settings-item-description-larger mt-0 mb-2"> <p className="settings-item-description-larger mt-0 mb-2" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('BlockedUsersInfo')} {lang('BlockedUsersInfo')}
</p> </p>
</div> </div>
@ -95,7 +95,7 @@ const SettingsPrivacyBlockedUsers: FC<StateProps & DispatchProps> = ({
{blockedIds!.map((contactId, i) => renderContact(contactId, i, 0))} {blockedIds!.map((contactId, i) => renderContact(contactId, i, 0))}
</div> </div>
) : blockedIds && !blockedIds.length ? ( ) : blockedIds && !blockedIds.length ? (
<div className="no-results"> <div className="no-results" dir="auto">
List is empty List is empty
</div> </div>
) : ( ) : (

View File

@ -150,7 +150,7 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps & DispatchProps> = ({
return ( return (
<div className="settings-content custom-scroll"> <div className="settings-content custom-scroll">
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header">{headerText}</h4> <h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{headerText}</h4>
<RadioGroup <RadioGroup
name={`visibility-${privacyKey}`} name={`visibility-${privacyKey}`}
@ -160,12 +160,12 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps & DispatchProps> = ({
/> />
{descriptionText && ( {descriptionText && (
<p className="settings-item-description-larger">{descriptionText}</p> <p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}>{descriptionText}</p>
)} )}
</div> </div>
<div className="settings-item"> <div className="settings-item">
<h4 className="settings-item-header mb-4">{lang('PrivacyExceptions')}</h4> <h4 className="settings-item-header mb-4" dir={lang.isRtl ? 'rtl' : undefined}>{lang('PrivacyExceptions')}</h4>
{exceptionLists.shouldShowAllowed && ( {exceptionLists.shouldShowAllowed && (
<ListItem <ListItem
@ -174,7 +174,7 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps & DispatchProps> = ({
onClick={() => { onScreenSelect(allowedContactsScreen); }} onClick={() => { onScreenSelect(allowedContactsScreen); }}
> >
<div className="multiline-menu-item full-size"> <div className="multiline-menu-item full-size">
{allowedCount > 0 && <span className="date">+{allowedCount}</span>} {allowedCount > 0 && <span className="date" dir="auto">+{allowedCount}</span>}
<span className="title">{lang('AlwaysShareWith')}</span> <span className="title">{lang('AlwaysShareWith')}</span>
<span className="subtitle">{lang('EditAdminAddUsers')}</span> <span className="subtitle">{lang('EditAdminAddUsers')}</span>
</div> </div>
@ -187,7 +187,7 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps & DispatchProps> = ({
onClick={() => { onScreenSelect(deniedContactsScreen); }} onClick={() => { onScreenSelect(deniedContactsScreen); }}
> >
<div className="multiline-menu-item full-size"> <div className="multiline-menu-item full-size">
{blockCount > 0 && <span className="date">&minus;{blockCount}</span>} {blockCount > 0 && <span className="date" dir="auto">&minus;{blockCount}</span>}
<span className="title">{lang('NeverShareWith')}</span> <span className="title">{lang('NeverShareWith')}</span>
<span className="subtitle">{lang('EditAdminAddUsers')}</span> <span className="subtitle">{lang('EditAdminAddUsers')}</span>
</div> </div>

View File

@ -22,4 +22,11 @@
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
} }
&[dir=rtl] {
.StickerButton,
.Button {
margin: 0 0 0 .5rem;
}
}
} }

View File

@ -45,6 +45,7 @@ const SettingsStickerSet: FC<OwnProps> = ({
<Button <Button
ariaLabel={stickerSet.title} ariaLabel={stickerSet.title}
color="translucent" color="translucent"
isRtl={lang.isRtl}
> >
{stickerSet.isAnimated ? ( {stickerSet.isAnimated ? (
<StickerSetCoverAnimated <StickerSetCoverAnimated

View File

@ -35,6 +35,12 @@
.status { .status {
display: none; display: none;
} }
&[dir=rtl] {
.title h3{
text-align: right;
}
}
} }
.ListItem-button { .ListItem-button {
@ -51,6 +57,13 @@
color: inherit; color: inherit;
} }
} }
&[dir=rtl] {
.Avatar {
margin-left: 1.5rem;
margin-right: -0.25rem;
}
}
} }
.settings-item .ShowMoreButton { .settings-item .ShowMoreButton {

View File

@ -42,6 +42,15 @@
font-weight: 400; font-weight: 400;
margin: 0; margin: 0;
} }
&[dir=rtl] {
.Checkbox {
margin-left: 0;
margin-right: auto;
padding-left: 0;
padding-right: 3.25rem;
}
}
} }
.settings-item-header { .settings-item-header {

View File

@ -192,10 +192,14 @@ const SettingsFoldersChatsPicker: FC<OwnProps> = ({
> >
{(!viewportIds || !viewportIds.length || viewportIds.includes(chatIds[0])) && ( {(!viewportIds || !viewportIds.length || viewportIds.includes(chatIds[0])) && (
<> <>
<h4 key="header1" className="settings-item-header">{lang('FilterChatTypes')}</h4> <h4 key="header1" className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('FilterChatTypes')}
</h4>
{chatTypes.map(renderChatType)} {chatTypes.map(renderChatType)}
<div key="divider" className="picker-list-divider" /> <div key="divider" className="picker-list-divider" />
<h4 key="header2" className="settings-item-header">{lang('FilterChats')}</h4> <h4 key="header2" className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('FilterChats')}
</h4>
</> </>
)} )}

View File

@ -236,7 +236,7 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps & DispatchProps> = ({
</div> </div>
{state.mode === 'create' && ( {state.mode === 'create' && (
<p className="settings-item-description mb-3"> <p className="settings-item-description mb-3" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('FilterIncludeInfo')} {lang('FilterIncludeInfo')}
</p> </p>
)} )}
@ -252,12 +252,12 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps & DispatchProps> = ({
<div className="settings-item no-border pt-3"> <div className="settings-item no-border pt-3">
{state.error && state.error === ERROR_NO_CHATS && ( {state.error && state.error === ERROR_NO_CHATS && (
<p className="settings-item-description color-danger mb-2"> <p className="settings-item-description color-danger mb-2" dir={lang.isRtl ? 'rtl' : undefined}>
{state.error} {state.error}
</p> </p>
)} )}
<h4 className="settings-item-header mb-3">{lang('FilterInclude')}</h4> <h4 className="settings-item-header mb-3" dir={lang.isRtl ? 'rtl' : undefined}>{lang('FilterInclude')}</h4>
<ListItem <ListItem
className="settings-folders-list-item color-primary mb-0" className="settings-folders-list-item color-primary mb-0"
@ -271,7 +271,7 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps & DispatchProps> = ({
</div> </div>
<div className="settings-item no-border pt-3"> <div className="settings-item no-border pt-3">
<h4 className="settings-item-header mb-3">{lang('FilterExclude')}</h4> <h4 className="settings-item-header mb-3" dir={lang.isRtl ? 'rtl' : undefined}>{lang('FilterExclude')}</h4>
<ListItem <ListItem
className="settings-folders-list-item color-primary mb-0" className="settings-folders-list-item color-primary mb-0"

View File

@ -151,6 +151,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
pill pill
fluid fluid
onClick={handleCreateFolder} onClick={handleCreateFolder}
isRtl={lang.isRtl}
> >
<i className="icon-add" /> <i className="icon-add" />
{lang('CreateNewFilter')} {lang('CreateNewFilter')}
@ -158,7 +159,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
</div> </div>
<div className="settings-item pt-3"> <div className="settings-item pt-3">
<h4 className="settings-item-header mb-3">{lang('Filters')}</h4> <h4 className="settings-item-header mb-3" dir={lang.isRtl ? 'rtl' : undefined}>{lang('Filters')}</h4>
{userFolders && userFolders.length ? userFolders.map((folder) => ( {userFolders && userFolders.length ? userFolders.map((folder) => (
<ListItem <ListItem
@ -171,7 +172,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
<span className="subtitle">{folder.subtitle}</span> <span className="subtitle">{folder.subtitle}</span>
</ListItem> </ListItem>
)) : userFolders && !userFolders.length ? ( )) : userFolders && !userFolders.length ? (
<p className="settings-item-description my-4"> <p className="settings-item-description my-4" dir="auto">
You have no folders yet. You have no folders yet.
</p> </p>
) : <Loading />} ) : <Loading />}
@ -179,7 +180,9 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
{(recommendedChatFolders && !!recommendedChatFolders.length) && ( {(recommendedChatFolders && !!recommendedChatFolders.length) && (
<div className="settings-item pt-3"> <div className="settings-item pt-3">
<h4 className="settings-item-header mb-3">{lang('FilterRecommended')}</h4> <h4 className="settings-item-header mb-3" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('FilterRecommended')}
</h4>
{recommendedChatFolders.map((folder) => ( {recommendedChatFolders.map((folder) => (
<ListItem <ListItem
@ -199,6 +202,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
size="tiny" size="tiny"
pill pill
fluid fluid
isRtl={lang.isRtl}
> >
{lang('Add')} {lang('Add')}
</Button> </Button>

View File

@ -18,9 +18,7 @@ type StateProps = {
animatedEmoji: ApiSticker; animatedEmoji: ApiSticker;
}; };
const SettingsTwoFaCongratulations: FC<OwnProps & StateProps> = ({ const SettingsTwoFaCongratulations: FC<OwnProps & StateProps> = ({ animatedEmoji, onScreenSelect }) => {
animatedEmoji, onScreenSelect,
}) => {
const lang = useLang(); const lang = useLang();
const handleClick = () => { const handleClick = () => {

View File

@ -19,9 +19,7 @@ type StateProps = {
animatedEmoji: ApiSticker; animatedEmoji: ApiSticker;
}; };
const SettingsTwoFaEnabled: FC<OwnProps & StateProps> = ({ const SettingsTwoFaEnabled: FC<OwnProps & StateProps> = ({ animatedEmoji, onScreenSelect }) => {
animatedEmoji, onScreenSelect,
}) => {
const lang = useLang(); const lang = useLang();
return ( return (

View File

@ -115,7 +115,7 @@ const ForwardPicker: FC<OwnProps & StateProps & DispatchProps> = ({
}, []); }, []);
const modalHeader = ( const modalHeader = (
<div className="modal-header"> <div className="modal-header" dir={lang.isRtl ? 'rtl' : undefined}>
<Button <Button
round round
color="translucent" color="translucent"

View File

@ -182,12 +182,24 @@
left: 0; left: 0;
background-image: url("../../assets/media_navigation_previous.svg"); background-image: url("../../assets/media_navigation_previous.svg");
background-position: 1.25rem calc(50% - 2rem); background-position: 1.25rem calc(50% - 2rem);
&[dir=rtl] {
left: auto;
right: 0;
transform: scaleX(-1);
}
} }
&.next { &.next {
right: 0; right: 0;
background-image: url("../../assets/media_navigation_next.svg"); background-image: url("../../assets/media_navigation_next.svg");
background-position: calc(100% - 1.25rem) calc(50% - 2rem); background-position: calc(100% - 1.25rem) calc(50% - 2rem);
&[dir=rtl]{
left: 0;
right: auto;
transform: scaleX(-1);
}
} }
&.inline { &.inline {

View File

@ -464,7 +464,7 @@ const MediaViewer: FC<StateProps & DispatchProps> = ({
> >
{() => ( {() => (
<> <>
<div className="media-viewer-head"> <div className="media-viewer-head" dir={lang.isRtl ? 'rtl' : undefined}>
{IS_MOBILE_SCREEN && ( {IS_MOBILE_SCREEN && (
<Button <Button
className="media-viewer-close" className="media-viewer-close"
@ -513,6 +513,7 @@ const MediaViewer: FC<StateProps & DispatchProps> = ({
type="button" type="button"
className={`navigation prev ${isVideo && !isGif && 'inline'}`} className={`navigation prev ${isVideo && !isGif && 'inline'}`}
aria-label={lang('AccDescrPrevious')} aria-label={lang('AccDescrPrevious')}
dir={lang.isRtl ? 'rtl' : undefined}
onClick={selectPreviousMedia} onClick={selectPreviousMedia}
/> />
)} )}
@ -521,6 +522,7 @@ const MediaViewer: FC<StateProps & DispatchProps> = ({
type="button" type="button"
className={`navigation next ${isVideo && !isGif && 'inline'}`} className={`navigation next ${isVideo && !isGif && 'inline'}`}
aria-label={lang('Next')} aria-label={lang('Next')}
dir={lang.isRtl ? 'rtl' : undefined}
onClick={selectNextMedia} onClick={selectNextMedia}
/> />
)} )}

View File

@ -1,10 +1,10 @@
.MediaViewerActions { .MediaViewerActions {
display: flex; display: flex;
margin-left: auto; margin-inline-start: auto;
margin-right: -.375rem; margin-inline-end: -.375rem;
.Button { .Button {
margin-left: .25rem; margin-inline-start: .25rem;
} }
} }

View File

@ -12,7 +12,7 @@
} }
.Avatar { .Avatar {
margin-right: 1rem; margin-inline-end: 1rem;
@media (max-width: 600px) { @media (max-width: 600px) {
display: none; display: none;

View File

@ -64,7 +64,7 @@ const AudioPlayer: FC<OwnProps & StateProps & DispatchProps> = ({
const audio = getMessageAudio(message); const audio = getMessageAudio(message);
return ( return (
<div className={buildClassName('AudioPlayer', className)}> <div className={buildClassName('AudioPlayer', className)} dir={lang.isRtl ? 'rtl' : undefined}>
<Button <Button
round round
ripple={!IS_MOBILE_SCREEN} ripple={!IS_MOBILE_SCREEN}

View File

@ -79,7 +79,7 @@ const HeaderPinnedMessage: FC<OwnProps> = ({
confirmLabel="Unpin" confirmLabel="Unpin"
confirmHandler={handleUnpinMessage} confirmHandler={handleUnpinMessage}
/> />
<div className="HeaderPinnedMessage" onClick={onClick}> <div className="HeaderPinnedMessage" onClick={onClick} dir={lang.isRtl ? 'rtl' : undefined}>
<PinnedMessageNavigation <PinnedMessageNavigation
count={count} count={count}
index={index} index={index}

View File

@ -345,8 +345,8 @@
} }
.icon-unpin { .icon-unpin {
margin-right: .75rem; margin-inline-start: -0.4375rem;
margin-left: -0.438rem; margin-inline-end: .75rem;
color: var(--color-text-secondary); color: var(--color-text-secondary);
font-size: 1.5rem; font-size: 1.5rem;
transition: color .15s transition: color .15s

View File

@ -270,7 +270,7 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
/> />
)} )}
{isPinnedMessageList && ( {isPinnedMessageList && (
<div className="unpin-button-container"> <div className="unpin-button-container" dir={lang.isRtl ? 'rtl' : undefined}>
<Button <Button
size="tiny" size="tiny"
fluid fluid

View File

@ -286,6 +286,11 @@
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
display: inline-block;
.ellipsis {
display: inline-flex;
}
&.online { &.online {
color: var(--color-primary); color: var(--color-primary);
@ -375,7 +380,7 @@
.message-text { .message-text {
overflow: hidden; overflow: hidden;
margin-left: 0.4rem; margin-inline-start: 0.375rem;
margin-top: 0.125rem; margin-top: 0.125rem;
max-width: 15rem; max-width: 15rem;
@ -391,6 +396,7 @@
color: var(--color-primary); color: var(--color-primary);
margin-bottom: 0.125rem; margin-bottom: 0.125rem;
white-space: pre; white-space: pre;
text-align: initial;
} }
p { p {
@ -414,7 +420,7 @@
height: 2.25rem; height: 2.25rem;
object-fit: cover; object-fit: cover;
border-radius: 0.25rem; border-radius: 0.25rem;
margin-left: 0.4rem; margin-inline-start: 0.375rem;
margin-top: 0.125rem; margin-top: 0.125rem;
flex-shrink: 0; flex-shrink: 0;

View File

@ -339,11 +339,13 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
withFullInfo={isChatWithBot} withFullInfo={isChatWithBot}
withMediaViewer withMediaViewer
withUpdatingStatus withUpdatingStatus
noRtl
/> />
) : ( ) : (
<GroupChatInfo <GroupChatInfo
chatId={chatId} chatId={chatId}
typingStatus={typingStatus} typingStatus={typingStatus}
noRtl
withMediaViewer withMediaViewer
withFullInfo withFullInfo
withUpdatingStatus withUpdatingStatus

View File

@ -11,6 +11,7 @@ import { formatIntegerCompact } from '../../util/textFormat';
import buildClassName from '../../util/buildClassName'; import buildClassName from '../../util/buildClassName';
import { pick } from '../../util/iteratees'; import { pick } from '../../util/iteratees';
import fastSmoothScroll from '../../util/fastSmoothScroll'; import fastSmoothScroll from '../../util/fastSmoothScroll';
import useLang from '../../hooks/useLang';
import Button from '../ui/Button'; import Button from '../ui/Button';
@ -37,6 +38,7 @@ const ScrollDownButton: FC<OwnProps & StateProps & DispatchProps> = ({
unreadCount, unreadCount,
focusLastMessage, focusLastMessage,
}) => { }) => {
const lang = useLang();
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const elementRef = useRef<HTMLDivElement>(null); const elementRef = useRef<HTMLDivElement>(null);
@ -72,7 +74,7 @@ const ScrollDownButton: FC<OwnProps & StateProps & DispatchProps> = ({
color="secondary" color="secondary"
round round
onClick={handleClick} onClick={handleClick}
ariaLabel="Scroll to bottom" ariaLabel={lang('AccDescrPageDown')}
> >
<i className="icon-arrow-down" /> <i className="icon-arrow-down" />
</Button> </Button>

View File

@ -70,8 +70,10 @@ const AttachMenu: FC<OwnProps> = ({
)} )}
{canAttachMedia && ( {canAttachMedia && (
<> <>
<MenuItem icon="photo" onClick={handleQuickSelect}>{lang('AttachmentMenu.PhotoOrVideo')}</MenuItem> <MenuItem icon="photo" onClick={handleQuickSelect}>
<MenuItem icon="document" onClick={handleDocumentSelect}>Document</MenuItem> {lang('AttachmentMenu.PhotoOrVideo')}
</MenuItem>
<MenuItem icon="document" onClick={handleDocumentSelect}>{lang('AttachDocument')}</MenuItem>
</> </>
)} )}
{canAttachPolls && ( {canAttachPolls && (

View File

@ -162,7 +162,7 @@ const AttachmentModal: FC<OwnProps> = ({
} }
return ( return (
<div className="modal-header-condensed"> <div className="modal-header-condensed" dir={lang.isRtl ? 'rtl' : undefined}>
<Button round color="translucent" size="smaller" ariaLabel="Cancel attachments" onClick={onClear}> <Button round color="translucent" size="smaller" ariaLabel="Cancel attachments" onClick={onClear}>
<i className="icon-close" /> <i className="icon-close" />
</Button> </Button>

View File

@ -286,6 +286,11 @@
@media (max-width: 600px) { @media (max-width: 600px) {
bottom: 0.6875rem; bottom: 0.6875rem;
} }
}
&[dir=rtl] .placeholder-text {
right: 0;
} }
.text-entity-link { .text-entity-link {

View File

@ -642,15 +642,15 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
const scheduledMaxDate = new Date(); const scheduledMaxDate = new Date();
scheduledMaxDate.setFullYear(scheduledMaxDate.getFullYear() + 1); scheduledMaxDate.setFullYear(scheduledMaxDate.getFullYear() + 1);
let sendButtonAriaLabel = 'Send message'; let sendButtonAriaLabel = 'SendMessage';
switch (mainButtonState) { switch (mainButtonState) {
case MainButtonState.Edit: case MainButtonState.Edit:
sendButtonAriaLabel = 'Save edited message'; sendButtonAriaLabel = 'Save edited message';
break; break;
case MainButtonState.Record: case MainButtonState.Record:
sendButtonAriaLabel = areVoiceMessagesNotAllowed sendButtonAriaLabel = areVoiceMessagesNotAllowed
? 'Posting media content is not allowed in this group.' ? 'Conversation.DefaultRestrictedMedia'
: 'Record a voice message'; : 'AccDescrVoiceMessage';
} }
const className = buildClassName( const className = buildClassName(
@ -867,7 +867,7 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
color="secondary" color="secondary"
className={`${mainButtonState} ${activeVoiceRecording ? 'recording' : ''}`} className={`${mainButtonState} ${activeVoiceRecording ? 'recording' : ''}`}
disabled={areVoiceMessagesNotAllowed} disabled={areVoiceMessagesNotAllowed}
ariaLabel={sendButtonAriaLabel} ariaLabel={lang(sendButtonAriaLabel)}
onClick={mainButtonHandler} onClick={mainButtonHandler}
onContextMenu={ onContextMenu={
mainButtonState === MainButtonState.Send && canShowCustomSendMenu ? handleContextMenu : undefined mainButtonState === MainButtonState.Send && canShowCustomSendMenu ? handleContextMenu : undefined

View File

@ -54,6 +54,7 @@ const EmojiCategory: FC<OwnProps> = ({
className={buildClassName('symbol-set-container', transitionClassNames)} className={buildClassName('symbol-set-container', transitionClassNames)}
// @ts-ignore // @ts-ignore
style={`height: ${height}px;`} style={`height: ${height}px;`}
dir={lang.isRtl ? 'rtl' : undefined}
> >
{shouldRender && category.emojis.map((name) => { {shouldRender && category.emojis.map((name) => {
const emoji = allEmojis[name]; const emoji = allEmojis[name];

View File

@ -194,7 +194,7 @@ const EmojiPicker: FC<OwnProps & StateProps> = ({
return ( return (
<div className={containerClassName}> <div className={containerClassName}>
<div ref={headerRef} className="EmojiPicker-header"> <div ref={headerRef} className="EmojiPicker-header" dir={lang.isRtl ? 'rtl' : ''}>
{allCategories.map(renderCategoryButton)} {allCategories.map(renderCategoryButton)}
</div> </div>
<div ref={containerRef} className="EmojiPicker-main no-selection no-scrollbar"> <div ref={containerRef} className="EmojiPicker-main no-selection no-scrollbar">

View File

@ -21,13 +21,20 @@
} }
.title { .title {
margin-right: 10px; margin-inline-end: .625rem;
max-width: 70%; max-width: 70%;
flex: 1 0 auto;
} }
.handle { .handle {
font-size: 1rem; font-size: 1rem;
} }
&[dir=rtl] {
.status {
width: auto;
}
}
} }
.ChatInfo { .ChatInfo {
@ -46,7 +53,7 @@
} }
.user-status { .user-status {
display: none; display: none !important;
} }
} }
} }

View File

@ -21,6 +21,7 @@ import useLayoutEffectWithPrevDeps from '../../../hooks/useLayoutEffectWithPrevD
import useFlag from '../../../hooks/useFlag'; import useFlag from '../../../hooks/useFlag';
import parseEmojiOnlyString from '../../common/helpers/parseEmojiOnlyString'; import parseEmojiOnlyString from '../../common/helpers/parseEmojiOnlyString';
import { isSelectionInsideInput } from './helpers/selection'; import { isSelectionInsideInput } from './helpers/selection';
import useLang from '../../../hooks/useLang';
import TextFormatter from './TextFormatter'; import TextFormatter from './TextFormatter';
@ -92,6 +93,7 @@ const MessageInput: FC<OwnProps & StateProps & DispatchProps> = ({
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const cloneRef = useRef<HTMLDivElement>(null); const cloneRef = useRef<HTMLDivElement>(null);
const lang = useLang();
const isContextMenuOpenRef = useRef(false); const isContextMenuOpenRef = useRef(false);
const [isTextFormatterOpen, openTextFormatter, closeTextFormatter] = useFlag(); const [isTextFormatterOpen, openTextFormatter, closeTextFormatter] = useFlag();
const [textFormatterAnchorPosition, setTextFormatterAnchorPosition] = useState<IAnchorPosition>(); const [textFormatterAnchorPosition, setTextFormatterAnchorPosition] = useState<IAnchorPosition>();
@ -351,13 +353,13 @@ const MessageInput: FC<OwnProps & StateProps & DispatchProps> = ({
); );
return ( return (
<div id={id} onClick={shouldSupressFocus ? onSupressedFocus : undefined}> <div id={id} onClick={shouldSupressFocus ? onSupressedFocus : undefined} dir={lang.isRtl ? 'rtl' : undefined}>
<div <div
ref={inputRef} ref={inputRef}
id={editableInputId || EDITABLE_INPUT_ID} id={editableInputId || EDITABLE_INPUT_ID}
className={className} className={className}
dir="auto"
contentEditable contentEditable
dir="auto"
onClick={focusInput} onClick={focusInput}
onChange={handleChange} onChange={handleChange}
onKeyDown={handleKeyDown} onKeyDown={handleKeyDown}

View File

@ -1,5 +1,7 @@
import React, { FC, memo, useCallback } from '../../../lib/teact/teact'; import React, { FC, memo, useCallback } from '../../../lib/teact/teact';
import useLang from '../../../hooks/useLang';
import Button from '../../ui/Button'; import Button from '../../ui/Button';
type OwnProps = { type OwnProps = {
@ -29,6 +31,8 @@ const SYMBOL_MENU_TAB_ICONS = {
const SymbolMenuFooter: FC<OwnProps> = ({ const SymbolMenuFooter: FC<OwnProps> = ({
activeTab, onSwitchTab, onRemoveSymbol, onSearchOpen, activeTab, onSwitchTab, onRemoveSymbol, onSearchOpen,
}) => { }) => {
const lang = useLang();
function renderTabButton(tab: SymbolMenuTabs) { function renderTabButton(tab: SymbolMenuTabs) {
return ( return (
<Button <Button
@ -53,7 +57,7 @@ const SymbolMenuFooter: FC<OwnProps> = ({
} }
return ( return (
<div className="SymbolMenu-footer" onClick={stopPropagation}> <div className="SymbolMenu-footer" onClick={stopPropagation} dir={lang.isRtl ? 'rtl' : undefined}>
{activeTab !== SymbolMenuTabs.Emoji && ( {activeTab !== SymbolMenuTabs.Emoji && (
<Button <Button
className="symbol-search-button" className="symbol-search-button"

View File

@ -6,6 +6,8 @@
width: 100%; width: 100%;
align-items: center; align-items: center;
padding: .5625rem .25rem .5625rem .625rem; padding: .5625rem .25rem .5625rem .625rem;
padding-inline-start: .625rem;
padding-inline-end: .25rem;
background: var(--background-color); background: var(--background-color);
border-bottom-right-radius: var(--border-bottom-right-radius); border-bottom-right-radius: var(--border-bottom-right-radius);
border-bottom-left-radius: var(--border-bottom-left-radius); border-bottom-left-radius: var(--border-bottom-left-radius);
@ -139,7 +141,6 @@
.message-content.poll &, .message-content.poll &,
.message-content.has-solid-background.text &, .message-content.has-solid-background.text &,
.message-content.has-solid-background.is-forwarded & { .message-content.has-solid-background.is-forwarded & {
margin-right: 0;
width: calc(100% + 1rem); width: calc(100% + 1rem);
} }
@ -150,25 +151,31 @@
.icon-comments { .icon-comments {
font-size: 1.5625rem; font-size: 1.5625rem;
line-height: 2rem; line-height: 2rem;
margin-right: .875rem; margin-inline-end: .875rem;
} }
.icon-next { .icon-next {
margin-left: auto; margin-inline-start: auto;
font-size: 1.5rem; font-size: 1.5rem;
} }
.recent-repliers { .recent-repliers {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
margin-right: .5rem; margin-inline-end: .5rem;
margin-left: -.125rem; margin-inline-start: -.125rem;
.Avatar { .Avatar {
transition: border .15s; transition: border .15s;
border: 2px solid var(--color-background); border: 2px solid var(--color-background);
margin-right: 0; margin-inline-end: 0;
z-index: 3; z-index: 3;
overflow: hidden;
.emoji {
width: 1rem;
background-size: 1rem;
}
+ .Avatar { + .Avatar {
z-index: 2; z-index: 2;
@ -179,7 +186,7 @@
} }
&:not(:first-child) { &:not(:first-child) {
margin-left: -.75rem; margin-inline-start: -.75rem;
} }
} }
} }
@ -194,7 +201,7 @@
height: .5rem; height: .5rem;
border-radius: 50%; border-radius: 50%;
background: var(--accent-color); background: var(--accent-color);
margin-left: .75rem; margin-inline-start: .75rem;
} }
} }

View File

@ -53,7 +53,7 @@ const CommentButton: FC<OwnProps & StateProps & DispatchProps> = ({
function renderRecentRepliers() { function renderRecentRepliers() {
return ( return (
recentRepliers && recentRepliers.length > 0 && ( recentRepliers && recentRepliers.length > 0 && (
<div className="recent-repliers"> <div className="recent-repliers" dir={lang.isRtl ? 'rtl' : 'ltr'}>
{recentRepliers.map((user) => ( {recentRepliers.map((user) => (
<Avatar <Avatar
key={user.id} key={user.id}
@ -73,6 +73,7 @@ const CommentButton: FC<OwnProps & StateProps & DispatchProps> = ({
<div <div
data-cnt={formatIntegerCompact(messagesCount)} data-cnt={formatIntegerCompact(messagesCount)}
className={buildClassName('CommentButton', hasUnread && 'has-unread', disabled && 'disabled')} className={buildClassName('CommentButton', hasUnread && 'has-unread', disabled && 'disabled')}
dir={lang.isRtl ? 'rtl' : 'ltr'}
onClick={handleClick} onClick={handleClick}
> >
<i className="icon-comments-sticker" /> <i className="icon-comments-sticker" />

View File

@ -659,11 +659,12 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
} }
return ( return (
<div className="message-title"> <div className="message-title" dir="ltr">
{senderTitle ? ( {senderTitle ? (
<span <span
className={buildClassName(senderPeer && 'interactive', senderColor)} className={buildClassName(senderPeer && 'interactive', senderColor)}
onClick={senderPeer ? handleSenderClick : undefined} onClick={senderPeer ? handleSenderClick : undefined}
dir="auto"
> >
{renderText(senderTitle)} {renderText(senderTitle)}
</span> </span>
@ -682,9 +683,9 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
</> </>
)} )}
{forwardInfo && forwardInfo.isLinkedChannelPost ? ( {forwardInfo && forwardInfo.isLinkedChannelPost ? (
<span className="admin-title">{lang('DiscussChannel')}</span> <span className="admin-title" dir="auto">{lang('DiscussChannel')}</span>
) : message.adminTitle && !isChannel ? ( ) : message.adminTitle && !isChannel ? (
<span className="admin-title">{message.adminTitle}</span> <span className="admin-title" dir="auto">{message.adminTitle}</span>
) : undefined} ) : undefined}
</div> </div>
); );
@ -789,6 +790,7 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
</Button> </Button>
) : undefined} ) : undefined}
{withCommentButton && <CommentButton message={message} disabled={noComments} />} {withCommentButton && <CommentButton message={message} disabled={noComments} />}
{contentClassName.includes('has-appendix') && <div className="svg-appendix" ref={appendixRef} />}
</div> </div>
{message.inlineButtons && ( {message.inlineButtons && (
<InlineButtons message={message} onClick={clickInlineButton} /> <InlineButtons message={message} onClick={clickInlineButton} />

View File

@ -123,10 +123,14 @@ const MessageContextMenu: FC<OwnProps> = ({
onCloseAnimationEnd={onCloseAnimationEnd} onCloseAnimationEnd={onCloseAnimationEnd}
> >
{canSendNow && <MenuItem icon="send-outline" onClick={onSend}>{lang('MessageScheduleSend')}</MenuItem>} {canSendNow && <MenuItem icon="send-outline" onClick={onSend}>{lang('MessageScheduleSend')}</MenuItem>}
{canReschedule && <MenuItem icon="schedule" onClick={onReschedule}>{lang('MessageScheduleEditTime')}</MenuItem>} {canReschedule && (
<MenuItem icon="schedule" onClick={onReschedule}>{lang('MessageScheduleEditTime')}</MenuItem>
)}
{canReply && <MenuItem icon="reply" onClick={onReply}>{lang('Reply')}</MenuItem>} {canReply && <MenuItem icon="reply" onClick={onReply}>{lang('Reply')}</MenuItem>}
{canEdit && <MenuItem icon="edit" onClick={onEdit}>{lang('Edit')}</MenuItem>} {canEdit && <MenuItem icon="edit" onClick={onEdit}>{lang('Edit')}</MenuItem>}
{canFaveSticker && <MenuItem icon="favorite" onClick={onFaveSticker}>{lang('AddToFavorites')}</MenuItem>} {canFaveSticker && (
<MenuItem icon="favorite" onClick={onFaveSticker}>{lang('AddToFavorites')}</MenuItem>
)}
{canUnfaveSticker && ( {canUnfaveSticker && (
<MenuItem icon="favorite" onClick={onUnfaveSticker}>{lang('Stickers.RemoveFromFavorites')}</MenuItem> <MenuItem icon="favorite" onClick={onUnfaveSticker}>{lang('Stickers.RemoveFromFavorites')}</MenuItem>
)} )}

View File

@ -24,7 +24,7 @@ const MessageMeta: FC<OwnProps> = ({
const lang = useLang(); const lang = useLang();
return ( return (
<span className="MessageMeta" onClick={onClick}> <span className="MessageMeta" dir={lang.isRtl ? 'rtl' : 'ltr'} onClick={onClick}>
{Boolean(message.views) && ( {Boolean(message.views) && (
<> <>
<span className="message-views"> <span className="message-views">

View File

@ -18,7 +18,7 @@ import { pick } from '../../../util/iteratees';
import renderText from '../../common/helpers/renderText'; import renderText from '../../common/helpers/renderText';
import { renderTextWithEntities } from '../../common/helpers/renderMessageText'; import { renderTextWithEntities } from '../../common/helpers/renderMessageText';
import { formatMediaDuration } from '../../../util/dateFormat'; import { formatMediaDuration } from '../../../util/dateFormat';
import useLang from '../../../hooks/useLang'; import useLang, { LangFn } from '../../../hooks/useLang';
import CheckboxGroup from '../../ui/CheckboxGroup'; import CheckboxGroup from '../../ui/CheckboxGroup';
import RadioGroup from '../../ui/RadioGroup'; import RadioGroup from '../../ui/RadioGroup';
@ -256,11 +256,11 @@ const Poll: FC<OwnProps & StateProps & DispatchProps> = ({
} }
return ( return (
<div className="Poll" dir="auto"> <div className="Poll" dir={lang.isRtl ? 'auto' : 'ltr'}>
{renderSolution()} {renderSolution()}
<div className="poll-question">{renderText(summary.question)}</div> <div className="poll-question">{renderText(summary.question)}</div>
<div className="poll-type"> <div className="poll-type">
{getPollTypeString(summary)} {lang(getPollTypeString(summary))}
{renderRecentVoters()} {renderRecentVoters()}
{closePeriod > 0 && canVote && <div ref={countdownRef} className="poll-countdown" />} {closePeriod > 0 && canVote && <div ref={countdownRef} className="poll-countdown" />}
{summary.quiz && poll.results.solution && !canVote && ( {summary.quiz && poll.results.solution && !canVote && (
@ -306,7 +306,7 @@ const Poll: FC<OwnProps & StateProps & DispatchProps> = ({
</div> </div>
)} )}
{!canViewResult && !isMultiple && ( {!canViewResult && !isMultiple && (
<div className="poll-voters-count">{getReadableVotersCount(summary.quiz, results.totalVoters)}</div> <div className="poll-voters-count">{getReadableVotersCount(lang, summary.quiz, results.totalVoters)}</div>
)} )}
{isMultiple && ( {isMultiple && (
<Button <Button
@ -338,22 +338,22 @@ function getPollTypeString(summary: ApiPoll['summary']) {
} }
if (summary.quiz) { if (summary.quiz) {
return summary.isPublic ? 'Quiz' : 'Anonymous Quiz'; return summary.isPublic ? 'QuizPoll' : 'AnonymousQuizPoll';
} }
if (summary.closed) { if (summary.closed) {
return 'Final results'; return 'FinalResults';
} }
return summary.isPublic ? 'Poll' : 'Anonymous Poll'; return summary.isPublic ? 'PublicPoll' : 'AnonymousPoll';
} }
function getReadableVotersCount(isQuiz: true | undefined, count?: number) { function getReadableVotersCount(lang: LangFn, isQuiz: true | undefined, count?: number) {
if (!count) { if (!count) {
return isQuiz ? 'No answers yet' : 'No voters yet'; return lang(isQuiz ? 'Chat.Quiz.TotalVotesEmpty' : 'Chat.Poll.TotalVotesResultEmpty');
} }
return isQuiz ? `${count} answered` : `${count} voted`; return lang(isQuiz ? 'Answer' : 'Vote', count, 'i');
} }
export default memo(withGlobal<OwnProps>( export default memo(withGlobal<OwnProps>(

View File

@ -15,7 +15,7 @@
position: relative; position: relative;
margin-top: .125rem; margin-top: .125rem;
width: 1.75rem; width: 1.75rem;
margin-right: .5rem; margin-inline-end: .5rem;
flex-shrink: 0; flex-shrink: 0;
font-weight: 500; font-weight: 500;
font-size: .875rem; font-size: .875rem;

View File

@ -67,7 +67,7 @@ const PollOption: FC<OwnProps> = ({
const lineStyle = `width: ${lineWidth}%; transform:scaleX(${isAnimationDoesNotStart ? 0 : 1})`; const lineStyle = `width: ${lineWidth}%; transform:scaleX(${isAnimationDoesNotStart ? 0 : 1})`;
return ( return (
<div className="PollOption"> <div className="PollOption" dir="ltr">
<div className={`poll-option-share ${answerPercent === '100' ? 'limit-width' : ''}`}> <div className={`poll-option-share ${answerPercent === '100' ? 'limit-width' : ''}`}>
{answerPercent}% {answerPercent}%
{showIcon && ( {showIcon && (
@ -82,7 +82,7 @@ const PollOption: FC<OwnProps> = ({
)} )}
</div> </div>
<div className="poll-option-right"> <div className="poll-option-right">
<div className="poll-option-text"> <div className="poll-option-text" dir="auto">
{renderText(answer.text)} {renderText(answer.text)}
</div> </div>
<div className={buildClassName('poll-option-answer', showIcon && !correctAnswer && 'wrong')}> <div className={buildClassName('poll-option-answer', showIcon && !correctAnswer && 'wrong')}>

View File

@ -59,6 +59,12 @@
order: 2; order: 2;
flex-shrink: 0; flex-shrink: 0;
} }
&:dir(rtl) {
.WebPage-text {
padding-inline-end: 1rem;
}
}
} }
&:not(.with-square-photo) { &:not(.with-square-photo) {
@ -95,6 +101,15 @@
line-height: 1rem; line-height: 1rem;
margin-bottom: 0; margin-bottom: 0;
} }
&:dir(rtl) {
padding-inline-start: .625rem;
&::before {
left: auto;
right: 0;
}
}
} }

View File

@ -41,17 +41,16 @@
} }
&.document:not(.text) { &.document:not(.text) {
& > .MessageMeta { &::after {
position: absolute; content: "";
top: auto; display: block;
bottom: 0 !important; clear: both;
right: .5rem;
} }
&:dir(rtl) {
& > .MessageMeta { & > .MessageMeta {
right: auto; position: relative;
left: .5rem; top: auto;
} bottom: -.5rem !important;
} }
} }
@ -142,8 +141,8 @@
.admin-title { .admin-title {
flex: 1; flex: 1;
margin-inline-start: 1rem; margin-left: 1rem;
text-align: end; text-align: right;
font-weight: 400; font-weight: 400;
font-size: 0.75rem; font-size: 0.75rem;
margin-top: -0.1rem; margin-top: -0.1rem;
@ -414,6 +413,7 @@
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
flex-direction: row-reverse; flex-direction: row-reverse;
direction: ltr;
> p { > p {
margin-bottom: 0; margin-bottom: 0;
@ -559,6 +559,15 @@
background: var(--accent-color); background: var(--accent-color);
border-radius: 2px; border-radius: 2px;
} }
&:dir(rtl) {
padding-inline-start: 0.625rem;
&::before {
left: auto;
right: 0.05rem;
}
}
} }
--border-top-left-radius: var(--border-radius-messages-small); --border-top-left-radius: var(--border-radius-messages-small);

View File

@ -18,6 +18,8 @@
h3 { h3 {
margin-bottom: 0; margin-bottom: 0;
margin-left: 1.5rem; margin-left: 1.5rem;
unicode-bidi: plaintext;
text-align: initial;
} }
} }

View File

@ -320,7 +320,7 @@ const Invoice: FC<OwnProps & StateProps & GlobalStateProps & DispatchProps> = ({
isOpen={isOpen} isOpen={isOpen}
onClose={onClose} onClose={onClose}
> >
<div className="header"> <div className="header" dir={lang.isRtl ? 'rtl' : undefined}>
<Button <Button
className="close-button" className="close-button"
color="translucent" color="translucent"

View File

@ -63,7 +63,7 @@ const ReceiptModal: FC<OwnProps & StateProps> = ({
onClose={onClose} onClose={onClose}
> >
<div> <div>
<div className="header"> <div className="header" dir={lang.isRtl ? 'rtl' : undefined}>
<Button <Button
className="close-button" className="close-button"
color="translucent" color="translucent"

View File

@ -97,7 +97,7 @@ const GifSearch: FC<StateProps & DispatchProps> = ({
const hasResults = Boolean(query !== undefined && results && results.length); const hasResults = Boolean(query !== undefined && results && results.length);
return ( return (
<div className="GifSearch"> <div className="GifSearch" dir={lang.isRtl ? 'rtl' : undefined}>
<InfiniteScroll <InfiniteScroll
ref={containerRef} ref={containerRef}
className={buildClassName('gif-container custom-scroll', hasResults && 'grid')} className={buildClassName('gif-container custom-scroll', hasResults && 'grid')}

View File

@ -23,6 +23,11 @@
.answer-percent { .answer-percent {
margin-left: auto; margin-left: auto;
&[dir=auto] {
margin-left: 0;
margin-right: auto;
}
} }
.poll-voters { .poll-voters {
@ -39,13 +44,29 @@
} }
} }
.chat-item-clickable .ChatInfo .Avatar.size-tiny { .chat-item-clickable {
margin-right: 1.75rem; .ChatInfo .Avatar.size-tiny {
margin-right: 1.75rem;
}
&[dir=rtl] {
.ChatInfo .Avatar.size-tiny {
margin-left: 1.75rem;
margin-right: 0;
}
}
} }
.ShowMoreButton { .ShowMoreButton {
margin: .25rem 0 0 -0.5rem; margin: .25rem 0 0 -0.5rem;
width: calc(100% + 1rem); width: calc(100% + 1rem);
&[dir=rtl] {
.icon-down {
margin-left: 2rem;
margin-right: 0;
}
}
} }
.icon-down { .icon-down {

View File

@ -12,6 +12,7 @@ import {
import { GlobalActions } from '../../global/types'; import { GlobalActions } from '../../global/types';
import { pick } from '../../util/iteratees'; import { pick } from '../../util/iteratees';
import usePrevious from '../../hooks/usePrevious'; import usePrevious from '../../hooks/usePrevious';
import useLang from '../../hooks/useLang';
import ShowMoreButton from '../ui/ShowMoreButton'; import ShowMoreButton from '../ui/ShowMoreButton';
import Loading from '../ui/Loading'; import Loading from '../ui/Loading';
@ -54,6 +55,7 @@ const PollAnswerResults: FC<OwnProps & StateProps & DispatchProps> = ({
const [isLoading, setIsLoading] = useState<boolean>(true); const [isLoading, setIsLoading] = useState<boolean>(true);
const areVotersLoaded = Boolean(voters); const areVotersLoaded = Boolean(voters);
const { option, text } = answer; const { option, text } = answer;
const lang = useLang();
useEffect(() => { useEffect(() => {
// For update when new votes arrive or when the user takes back his vote // For update when new votes arrive or when the user takes back his vote
@ -115,9 +117,11 @@ const PollAnswerResults: FC<OwnProps & StateProps & DispatchProps> = ({
: <Loading />} : <Loading />}
{voters && renderViewMoreButton()} {voters && renderViewMoreButton()}
</div> </div>
<div className="answer-head"> <div className="answer-head" dir={lang.isRtl ? 'rtl' : undefined}>
<span className="answer-title" dir="auto">{text}</span> <span className="answer-title" dir="auto">{text}</span>
<span className="answer-percent">{getPercentage(answerVote.votersCount, totalVoters)}%</span> <span className="answer-percent" dir={lang.isRtl ? 'auto' : undefined}>
{getPercentage(answerVote.votersCount, totalVoters)}%
</span>
</div> </div>
</div> </div>
); );

View File

@ -5,6 +5,7 @@ import { ApiMessage, ApiChat } from '../../api/types';
import { selectChat, selectChatMessage } from '../../modules/selectors'; import { selectChat, selectChatMessage } from '../../modules/selectors';
import { buildCollectionByKey } from '../../util/iteratees'; import { buildCollectionByKey } from '../../util/iteratees';
import { getMessagePoll } from '../../modules/helpers'; import { getMessagePoll } from '../../modules/helpers';
import useLang from '../../hooks/useLang';
import PollAnswerResults from './PollAnswerResults'; import PollAnswerResults from './PollAnswerResults';
import Loading from '../ui/Loading'; import Loading from '../ui/Loading';
@ -22,6 +23,7 @@ const PollResults: FC<StateProps> = ({
message, message,
lastSyncTime, lastSyncTime,
}) => { }) => {
const lang = useLang();
if (!message || !chat) { if (!message || !chat) {
return <Loading />; return <Loading />;
} }
@ -34,7 +36,7 @@ const PollResults: FC<StateProps> = ({
const resultsByOption = buildCollectionByKey(results.results, 'option'); const resultsByOption = buildCollectionByKey(results.results, 'option');
return ( return (
<div className="PollResults"> <div className="PollResults" dir={lang.isRtl ? 'rtl' : undefined}>
<h3 className="poll-question" dir="auto">{summary.question}</h3> <h3 className="poll-question" dir="auto">{summary.question}</h3>
<div className="poll-results-list custom-scroll"> <div className="poll-results-list custom-scroll">
{lastSyncTime && summary.answers.map((answer) => ( {lastSyncTime && summary.answers.map((answer) => (

View File

@ -32,6 +32,12 @@
.Switcher { .Switcher {
margin-left: auto; margin-left: auto;
} }
[dir=rtl] {
.Switcher {
margin-left: 0;
margin-right: auto;
}
}
} }
} }

View File

@ -120,9 +120,7 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
const containerRef = useRef<HTMLDivElement>(null); const containerRef = useRef<HTMLDivElement>(null);
// eslint-disable-next-line no-null/no-null // eslint-disable-next-line no-null/no-null
const transitionRef = useRef<HTMLDivElement>(null); const transitionRef = useRef<HTMLDivElement>(null);
const lang = useLang(); const lang = useLang();
const [activeTab, setActiveTab] = useState(0); const [activeTab, setActiveTab] = useState(0);
const tabs = useMemo(() => ([ const tabs = useMemo(() => ([
@ -251,7 +249,11 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
} }
return ( return (
<div className={`content ${resultType}-list`} teactFastList> <div
className={`content ${resultType}-list`}
dir={lang.isRtl && resultType === 'media' ? 'rtl' : undefined}
teactFastList
>
{resultType === 'media' ? ( {resultType === 'media' ? (
viewportIds!.map((id) => chatMessages[id] && ( viewportIds!.map((id) => chatMessages[id] && (
<Media <Media
@ -328,7 +330,7 @@ const Profile: FC<OwnProps & StateProps & DispatchProps> = ({
<div className="shared-media"> <div className="shared-media">
<Transition <Transition
ref={transitionRef} ref={transitionRef}
name="slide" name={lang.isRtl ? 'slide-reversed' : 'slide'}
activeKey={activeKey} activeKey={activeKey}
renderCount={tabs.length} renderCount={tabs.length}
shouldRestoreHeight shouldRestoreHeight

View File

@ -99,6 +99,20 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: flex-end; justify-content: flex-end;
&:dir(rtl) {
.status {
text-align: initial;
unicode-bidi: plaintext;
}
}
&[dir=rtl] {
.status {
text-align: initial;
unicode-bidi: plaintext;
}
}
} }
.title { .title {
@ -130,4 +144,18 @@
font-size: 0.875rem; font-size: 0.875rem;
opacity: .5; opacity: .5;
} }
&[dir=rtl] {
.navigation.prev {
left: auto;
right: 0;
transform: scaleX(-1);
}
.navigation.next {
left: 0;
right: auto;
transform: scaleX(-1);
}
}
} }

View File

@ -175,7 +175,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
const isVerifiedIconShown = (user && user.isVerified) || (chat && chat.isVerified); const isVerifiedIconShown = (user && user.isVerified) || (chat && chat.isVerified);
return ( return (
<div className="ProfileInfo"> <div className="ProfileInfo" dir={lang.isRtl ? 'rtl' : undefined}>
<div className="photo-wrapper"> <div className="photo-wrapper">
{renderPhotoTabs()} {renderPhotoTabs()}
<Transition activeKey={currentPhotoIndex} name={slideAnimation} className="profile-slide-container"> <Transition activeKey={currentPhotoIndex} name={slideAnimation} className="profile-slide-container">
@ -200,7 +200,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
)} )}
</div> </div>
<div className="info"> <div className="info" dir={lang.isRtl ? 'rtl' : 'auto'}>
{isSavedMessages ? ( {isSavedMessages ? (
<div className="title"> <div className="title">
<h3 dir="auto">{lang('SavedMessages')}</h3> <h3 dir="auto">{lang('SavedMessages')}</h3>
@ -223,9 +223,7 @@ export default memo(withGlobal<OwnProps>(
const user = selectUser(global, userId); const user = selectUser(global, userId);
const chat = selectChat(global, userId); const chat = selectChat(global, userId);
const isSavedMessages = !forceShowSelf && user && user.isSelf; const isSavedMessages = !forceShowSelf && user && user.isSelf;
const { const { animationLevel } = global.settings.byKey;
animationLevel,
} = global.settings.byKey;
return { return {
lastSyncTime, user, chat, isSavedMessages, animationLevel, lastSyncTime, user, chat, isSavedMessages, animationLevel,

View File

@ -104,7 +104,10 @@ const RightSearch: FC<OwnProps & StateProps & DispatchProps> = ({
const text = getMessageSummaryText(lang, message); const text = getMessageSummaryText(lang, message);
return ( return (
<ListItem className="chat-item-clickable search-result-message m-0" onClick={onClick}> <ListItem
className="chat-item-clickable search-result-message m-0"
onClick={onClick}
>
<Avatar chat={senderChat} user={senderUser} /> <Avatar chat={senderChat} user={senderUser} />
<div className="info"> <div className="info">
<div className="title"> <div className="title">

Some files were not shown because too many files have changed in this diff Show More