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) {
return undefined;
} else if (update.action instanceof GramJs.SendMessageTypingAction) {
action = 'typing';
action = 'lng_user_typing';
} else if (update.action instanceof GramJs.SendMessageRecordVideoAction) {
action = 'recording a video';
action = 'lng_send_action_record_video';
} else if (update.action instanceof GramJs.SendMessageUploadVideoAction) {
action = 'uploading a video';
action = 'lng_send_action_upload_video';
} 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) {
action = 'uploading a voice message';
action = 'lng_send_action_upload_audio';
} else if (update.action instanceof GramJs.SendMessageUploadPhotoAction) {
action = 'uploading a photo';
action = 'lng_send_action_upload_photo';
} else if (update.action instanceof GramJs.SendMessageUploadDocumentAction) {
action = 'uploading a file';
action = 'lng_send_action_upload_file';
} else if (update.action instanceof GramJs.SendMessageGeoLocationAction) {
action = 'selecting a location to share';
} else if (update.action instanceof GramJs.SendMessageChooseContactAction) {
action = 'selecting a contact to share';
} else if (update.action instanceof GramJs.SendMessageGamePlayAction) {
action = 'playing a game';
action = 'lng_playing_game';
} 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) {
action = 'uploading a round video';
action = 'lng_send_action_upload_round';
}
return {

View File

@ -232,10 +232,34 @@
top: 0.1875rem;
left: 0.1875rem;
}
&[dir=rtl] {
.media-loading {
left: auto !important;
right: 0;
}
}
}
.ProgressSpinner.size-s svg {
width: 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 isVoice = Boolean(voice);
const isSeeking = useRef<boolean>(false);
const lang = useLang();
const [isActivated, setIsActivated] = useState(false);
const shouldDownload = (isActivated || PRELOAD) && lastSyncTime;
@ -177,8 +178,6 @@ const Audio: FC<OwnProps & StateProps> = ({
onDateClick!(message.id, message.chatId);
}, [onDateClick, message.id, message.chatId]);
const lang = useLang();
function getFirstLine() {
if (isVoice) {
return senderTitle || 'Voice';
@ -264,7 +263,7 @@ const Audio: FC<OwnProps & StateProps> = ({
}
return (
<div className={fullClassName}>
<div className={fullClassName} dir={lang.isRtl ? 'rtl' : undefined}>
{isSelectable && (
<div className="message-select-control">
{isSelected && <i className="icon-select" />}
@ -277,6 +276,7 @@ const Audio: FC<OwnProps & StateProps> = ({
className={buttonClassNames.join(' ')}
ariaLabel={isPlaying ? 'Pause audio' : 'Play audio'}
onClick={handleButtonClick}
isRtl={lang.isRtl}
>
<i className="icon-play" />
<i className="icon-pause" />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -117,4 +117,29 @@
opacity: 0;
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}
onClick={() => onClick(clickArg)}
title={isMinimized ? titleText : undefined}
dir={lang.isRtl ? 'rtl' : undefined}
>
{iconElement}
{!isMinimized && (

View File

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

View File

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

View File

@ -4,7 +4,7 @@
.sender-name {
&::after {
content: '\00a0is\00a0';
content: '\00a0';
color: var(--color-text-secondary);
}
}
@ -17,6 +17,11 @@
&::after {
content: '...';
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);
}
}
@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 { getUserFirstOrLastName } from '../../modules/helpers';
import renderText from './helpers/renderText';
import useLang from '../../hooks/useLang';
import './TypingStatus.scss';
@ -18,14 +19,16 @@ type StateProps = {
};
const TypingStatus: FC<OwnProps & StateProps> = ({ typingStatus, typingUser }) => {
const lang = useLang();
const typingUserName = typingUser && !typingUser.isSelf && getUserFirstOrLastName(typingUser);
return (
<p className="typing-status">
<p className="typing-status" dir={lang.isRtl ? 'rtl' : 'auto'}>
{typingUserName && (
<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" />
</p>
);

View File

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

View File

@ -42,6 +42,7 @@
.site-description,
.site-title {
word-break: break-word;
text-align: initial;
}
.site-name {
@ -80,4 +81,18 @@
width: 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
className={className}
data-initial={(siteName || displayUrl)[0]}
dir={lang.isRtl ? 'rtl' : undefined}
>
{photo && (
<Media message={message} />
)}
<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 && (
<Link className="site-description" onClick={handleMessageClick}>{renderText(truncatedDescription)}</Link>
<Link isRtl={lang.isRtl} className="site-description" onClick={handleMessageClick}>
{renderText(truncatedDescription)}
</Link>
)}
<SafeLink
url={url}
className="site-name"
text=""
isRtl={lang.isRtl}
>
{url.replace('mailto:', '') || displayUrl}
</SafeLink>
@ -93,6 +99,7 @@ const WebLink: FC<OwnProps> = ({ message, senderTitle, onMessageClick }) => {
<Link
className="date"
onClick={handleMessageClick}
isRtl={lang.isRtl}
>
{formatPastTimeShort(lang, message.date * 1000)}
</Link>

View File

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

View File

@ -15,13 +15,13 @@
color: var(--color-text-lighter);
font-weight: 500;
line-height: 2rem;
margin-left: 1.9rem;
margin-inline-start: 1.875rem;
white-space: nowrap;
}
@media (max-width: 950px) {
> .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';
return isConnecting && (
<div id="ConnectionState">
<div id="ConnectionState" dir={lang.isRtl ? 'rtl' : undefined}>
<Spinner color="black" />
<div className="state-text">{lang('WaitingForNetwork')}</div>
</div>

View File

@ -94,11 +94,14 @@
}
.last-message {
.sender-name, .draft {
.draft {
&::after {
content: ': ';
}
}
.colon {
margin-inline-end: .25rem;
}
.media-preview {
position: relative;
@ -110,7 +113,7 @@
object-fit: cover;
border-radius: .125rem;
vertical-align: -.25rem;
margin-right: .25rem;
margin-inline-end: .25rem;
&.round {
border-radius: .625rem;
@ -127,8 +130,34 @@
font-size: .75rem;
color: #fff;
position: absolute;
left: .25rem;
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 (
<p className="last-message" dir="auto">
{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)}
</p>

View File

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

View File

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

View File

@ -88,7 +88,9 @@ const AudioResults: FC<OwnProps & StateProps & DispatchProps> = ({
key={message.id}
>
{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
key={message.id}

View File

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

View File

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

View File

@ -41,6 +41,19 @@
left: .625rem;
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,
@ -97,6 +110,7 @@
.handle {
unicode-bidi: plaintext;
color: var(--color-primary);
unicode-bidi: plaintext;
&::before {
content: '@';
@ -115,6 +129,16 @@
}
}
}
&[dir=rtl] {
.status {
text-align: right;
.handle {
float: right;
}
}
}
}
}
@ -156,6 +180,15 @@
color: var(--color-links-hover);
}
}
&[dir=rtl],
&[dir=auto] {
.Link {
float: left;
margin-left: 1rem;
margin-right: 0;
}
}
}
.Loading {
@ -186,9 +219,23 @@
margin-right: auto;
}
}
&[dir=rtl] {
> .PickerSelectedItem:last-child {
margin-left: auto;
margin-right: 0;
}
}
}
.NothingFound {
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 { parseDateString } from '../../../util/dateFormat';
import useLang from '../../../hooks/useLang';
import TabList from '../../ui/TabList';
import Transition from '../../ui/Transition';
@ -58,6 +59,7 @@ const LeftSearch: FC<OwnProps & StateProps & DispatchProps> = ({
setGlobalSearchDate,
onReset,
}) => {
const lang = useLang();
const [activeTab, setActiveTab] = useState(0);
const dateSearchQuery = useMemo(() => parseDateString(searchQuery), [searchQuery]);
@ -74,7 +76,11 @@ const LeftSearch: FC<OwnProps & StateProps & DispatchProps> = ({
return (
<div className="LeftSearch">
<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) {
case GlobalSearchContent.ChatList:

View File

@ -78,10 +78,13 @@ const LinkResults: FC<OwnProps & StateProps & DispatchProps> = ({
return (
<div
className="ListItem"
dir={lang.isRtl ? 'rtl' : undefined}
key={message.id}
>
{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
key={message.id}

View File

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

View File

@ -64,7 +64,7 @@
width: 1rem;
height: 1rem;
background-size: 1rem;
margin-right: 1px;
margin-inline-end: 1px;
vertical-align: -3px;
}
}
@ -77,5 +77,12 @@
.Button {
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 (
<div className="RecentContacts custom-scroll">
{topUserIds && (
<div className="top-peers-section">
<div className="top-peers-section" dir={lang.isRtl ? 'rtl' : undefined}>
<div ref={topUsersRef} className="top-peers no-selection">
{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]} />
<div className="top-peer-name">{renderText(getUserFirstOrLastName(usersById[userId]) || NBSP)}</div>
</div>
@ -87,7 +87,7 @@ const RecentContacts: FC<OwnProps & StateProps & DispatchProps> = ({
)}
{recentlyFoundChatIds && (
<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')}
<Button
@ -96,6 +96,7 @@ const RecentContacts: FC<OwnProps & StateProps & DispatchProps> = ({
color="translucent"
ariaLabel="Clear recent chats"
onClick={clearRecentlyFoundChats}
isRtl={lang.isRtl}
>
<i className="icon-close" />
</Button>

View File

@ -129,6 +129,10 @@
color: var(--color-text-secondary);
margin-bottom: 2rem;
position: relative;
&[dir=rtl] {
text-align: right;
}
}
&-description {
@ -140,6 +144,11 @@
.settings-content.two-fa & {
font-size: 1rem;
}
&[dir=rtl] {
text-align: right;
unicode-bidi: plaintext;
}
}
&-description-larger {
@ -148,6 +157,10 @@
color: var(--color-text-secondary);
margin-top: 2rem;
margin-bottom: 0.75rem;
&[dir=rtl] {
text-align: right;
}
}
.ListItem {
@ -238,6 +251,20 @@
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 {

View File

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

View File

@ -109,7 +109,7 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
return (
<div className="settings-content custom-scroll">
<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
label={lang('TextSize')}
@ -128,10 +128,12 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
</div>
<div className="settings-item">
<h4 className="settings-item-header">
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>
Animation Level
</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
options={ANIMATION_LEVEL_OPTIONS}
@ -142,7 +144,7 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
{KEYBOARD_SEND_OPTIONS && (
<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
name="keyboard-send-settings"
@ -154,7 +156,7 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
)}
<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
label={lang('Contacts')}
@ -179,7 +181,7 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
</div>
<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
label={lang('GifsTab2')}
@ -194,7 +196,7 @@ const SettingsGeneral: FC<OwnProps & StateProps & DispatchProps> = ({
</div>
<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
label={lang('SuggestStickers')}

View File

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

View File

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

View File

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

View File

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

View File

@ -76,7 +76,9 @@ const SettingsNotifications: FC<StateProps & DispatchProps> = ({
return (
<div className="settings-content custom-scroll">
<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
label={lang('NotificationsForPrivateChats')}
@ -93,7 +95,7 @@ const SettingsNotifications: FC<StateProps & DispatchProps> = ({
</div>
<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
label={lang('NotificationsForGroups')}
@ -110,7 +112,7 @@ const SettingsNotifications: FC<StateProps & DispatchProps> = ({
</div>
<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
label={lang('NotificationsForChannels')}
@ -127,7 +129,7 @@ const SettingsNotifications: FC<StateProps & DispatchProps> = ({
</div>
<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
label={lang('ContactJoined')}

View File

@ -111,7 +111,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
</div>
<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
narrow

View File

@ -55,11 +55,13 @@ const SettingsPrivacyActiveSessions: FC<StateProps & DispatchProps> = ({
function renderCurrentSession(session: ApiSession) {
return (
<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>
<div className="multiline-menu-item">
<span className="title">{session.appName}</span>
<div className="multiline-menu-item" dir="auto">
<span className="title" dir="auto">{session.appName}</span>
<span className="subtitle black tight">{getDeviceEnvironment(session)}</span>
<span className="subtitle">{session.ip} - {getLocation(session)}</span>
</div>
@ -81,7 +83,7 @@ const SettingsPrivacyActiveSessions: FC<StateProps & DispatchProps> = ({
function renderOtherSessions(sessions: ApiSession[]) {
return (
<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)}
</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="title">{session.appName}</span>
<span className="subtitle black tight">{getDeviceEnvironment(session)}</span>
@ -141,7 +143,11 @@ function getDeviceEnvironment(session: ApiSession) {
}
export default memo(withGlobal(
(global): StateProps => ({ activeSessions: global.activeSessions }),
(global): StateProps => {
return {
activeSessions: global.activeSessions,
};
},
(setGlobal, actions): DispatchProps => pick(actions, [
'loadAuthorizations', 'terminateAuthorization', 'terminateAllAuthorizations',
]),

View File

@ -84,7 +84,7 @@ const SettingsPrivacyBlockedUsers: FC<StateProps & DispatchProps> = ({
<div className="settings-fab-wrapper">
<div className="settings-content infinite-scroll">
<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')}
</p>
</div>
@ -95,7 +95,7 @@ const SettingsPrivacyBlockedUsers: FC<StateProps & DispatchProps> = ({
{blockedIds!.map((contactId, i) => renderContact(contactId, i, 0))}
</div>
) : blockedIds && !blockedIds.length ? (
<div className="no-results">
<div className="no-results" dir="auto">
List is empty
</div>
) : (

View File

@ -150,7 +150,7 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps & DispatchProps> = ({
return (
<div className="settings-content custom-scroll">
<div className="settings-item">
<h4 className="settings-item-header">{headerText}</h4>
<h4 className="settings-item-header" dir={lang.isRtl ? 'rtl' : undefined}>{headerText}</h4>
<RadioGroup
name={`visibility-${privacyKey}`}
@ -160,12 +160,12 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps & DispatchProps> = ({
/>
{descriptionText && (
<p className="settings-item-description-larger">{descriptionText}</p>
<p className="settings-item-description-larger" dir={lang.isRtl ? 'rtl' : undefined}>{descriptionText}</p>
)}
</div>
<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 && (
<ListItem
@ -174,7 +174,7 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps & DispatchProps> = ({
onClick={() => { onScreenSelect(allowedContactsScreen); }}
>
<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="subtitle">{lang('EditAdminAddUsers')}</span>
</div>
@ -187,7 +187,7 @@ const SettingsPrivacyVisibility: FC<OwnProps & StateProps & DispatchProps> = ({
onClick={() => { onScreenSelect(deniedContactsScreen); }}
>
<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="subtitle">{lang('EditAdminAddUsers')}</span>
</div>

View File

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

View File

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

View File

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

View File

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

View File

@ -192,10 +192,14 @@ const SettingsFoldersChatsPicker: FC<OwnProps> = ({
>
{(!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)}
<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>
{state.mode === 'create' && (
<p className="settings-item-description mb-3">
<p className="settings-item-description mb-3" dir={lang.isRtl ? 'rtl' : undefined}>
{lang('FilterIncludeInfo')}
</p>
)}
@ -252,12 +252,12 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps & DispatchProps> = ({
<div className="settings-item no-border pt-3">
{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}
</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
className="settings-folders-list-item color-primary mb-0"
@ -271,7 +271,7 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps & DispatchProps> = ({
</div>
<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
className="settings-folders-list-item color-primary mb-0"

View File

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

View File

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

View File

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

View File

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

View File

@ -182,12 +182,24 @@
left: 0;
background-image: url("../../assets/media_navigation_previous.svg");
background-position: 1.25rem calc(50% - 2rem);
&[dir=rtl] {
left: auto;
right: 0;
transform: scaleX(-1);
}
}
&.next {
right: 0;
background-image: url("../../assets/media_navigation_next.svg");
background-position: calc(100% - 1.25rem) calc(50% - 2rem);
&[dir=rtl]{
left: 0;
right: auto;
transform: scaleX(-1);
}
}
&.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 && (
<Button
className="media-viewer-close"
@ -513,6 +513,7 @@ const MediaViewer: FC<StateProps & DispatchProps> = ({
type="button"
className={`navigation prev ${isVideo && !isGif && 'inline'}`}
aria-label={lang('AccDescrPrevious')}
dir={lang.isRtl ? 'rtl' : undefined}
onClick={selectPreviousMedia}
/>
)}
@ -521,6 +522,7 @@ const MediaViewer: FC<StateProps & DispatchProps> = ({
type="button"
className={`navigation next ${isVideo && !isGif && 'inline'}`}
aria-label={lang('Next')}
dir={lang.isRtl ? 'rtl' : undefined}
onClick={selectNextMedia}
/>
)}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -162,7 +162,7 @@ const AttachmentModal: FC<OwnProps> = ({
}
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}>
<i className="icon-close" />
</Button>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,6 +6,8 @@
width: 100%;
align-items: center;
padding: .5625rem .25rem .5625rem .625rem;
padding-inline-start: .625rem;
padding-inline-end: .25rem;
background: var(--background-color);
border-bottom-right-radius: var(--border-bottom-right-radius);
border-bottom-left-radius: var(--border-bottom-left-radius);
@ -139,7 +141,6 @@
.message-content.poll &,
.message-content.has-solid-background.text &,
.message-content.has-solid-background.is-forwarded & {
margin-right: 0;
width: calc(100% + 1rem);
}
@ -150,25 +151,31 @@
.icon-comments {
font-size: 1.5625rem;
line-height: 2rem;
margin-right: .875rem;
margin-inline-end: .875rem;
}
.icon-next {
margin-left: auto;
margin-inline-start: auto;
font-size: 1.5rem;
}
.recent-repliers {
display: inline-flex;
align-items: center;
margin-right: .5rem;
margin-left: -.125rem;
margin-inline-end: .5rem;
margin-inline-start: -.125rem;
.Avatar {
transition: border .15s;
border: 2px solid var(--color-background);
margin-right: 0;
margin-inline-end: 0;
z-index: 3;
overflow: hidden;
.emoji {
width: 1rem;
background-size: 1rem;
}
+ .Avatar {
z-index: 2;
@ -179,7 +186,7 @@
}
&:not(:first-child) {
margin-left: -.75rem;
margin-inline-start: -.75rem;
}
}
}
@ -194,7 +201,7 @@
height: .5rem;
border-radius: 50%;
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() {
return (
recentRepliers && recentRepliers.length > 0 && (
<div className="recent-repliers">
<div className="recent-repliers" dir={lang.isRtl ? 'rtl' : 'ltr'}>
{recentRepliers.map((user) => (
<Avatar
key={user.id}
@ -73,6 +73,7 @@ const CommentButton: FC<OwnProps & StateProps & DispatchProps> = ({
<div
data-cnt={formatIntegerCompact(messagesCount)}
className={buildClassName('CommentButton', hasUnread && 'has-unread', disabled && 'disabled')}
dir={lang.isRtl ? 'rtl' : 'ltr'}
onClick={handleClick}
>
<i className="icon-comments-sticker" />

View File

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

View File

@ -123,10 +123,14 @@ const MessageContextMenu: FC<OwnProps> = ({
onCloseAnimationEnd={onCloseAnimationEnd}
>
{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>}
{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 && (
<MenuItem icon="favorite" onClick={onUnfaveSticker}>{lang('Stickers.RemoveFromFavorites')}</MenuItem>
)}

View File

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

View File

@ -18,7 +18,7 @@ import { pick } from '../../../util/iteratees';
import renderText from '../../common/helpers/renderText';
import { renderTextWithEntities } from '../../common/helpers/renderMessageText';
import { formatMediaDuration } from '../../../util/dateFormat';
import useLang from '../../../hooks/useLang';
import useLang, { LangFn } from '../../../hooks/useLang';
import CheckboxGroup from '../../ui/CheckboxGroup';
import RadioGroup from '../../ui/RadioGroup';
@ -256,11 +256,11 @@ const Poll: FC<OwnProps & StateProps & DispatchProps> = ({
}
return (
<div className="Poll" dir="auto">
<div className="Poll" dir={lang.isRtl ? 'auto' : 'ltr'}>
{renderSolution()}
<div className="poll-question">{renderText(summary.question)}</div>
<div className="poll-type">
{getPollTypeString(summary)}
{lang(getPollTypeString(summary))}
{renderRecentVoters()}
{closePeriod > 0 && canVote && <div ref={countdownRef} className="poll-countdown" />}
{summary.quiz && poll.results.solution && !canVote && (
@ -306,7 +306,7 @@ const Poll: FC<OwnProps & StateProps & DispatchProps> = ({
</div>
)}
{!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 && (
<Button
@ -338,22 +338,22 @@ function getPollTypeString(summary: ApiPoll['summary']) {
}
if (summary.quiz) {
return summary.isPublic ? 'Quiz' : 'Anonymous Quiz';
return summary.isPublic ? 'QuizPoll' : 'AnonymousQuizPoll';
}
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) {
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>(

View File

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

View File

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

View File

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

View File

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

View File

@ -18,6 +18,8 @@
h3 {
margin-bottom: 0;
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}
onClose={onClose}
>
<div className="header">
<div className="header" dir={lang.isRtl ? 'rtl' : undefined}>
<Button
className="close-button"
color="translucent"

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@ import {
import { GlobalActions } from '../../global/types';
import { pick } from '../../util/iteratees';
import usePrevious from '../../hooks/usePrevious';
import useLang from '../../hooks/useLang';
import ShowMoreButton from '../ui/ShowMoreButton';
import Loading from '../ui/Loading';
@ -54,6 +55,7 @@ const PollAnswerResults: FC<OwnProps & StateProps & DispatchProps> = ({
const [isLoading, setIsLoading] = useState<boolean>(true);
const areVotersLoaded = Boolean(voters);
const { option, text } = answer;
const lang = useLang();
useEffect(() => {
// 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 />}
{voters && renderViewMoreButton()}
</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-percent">{getPercentage(answerVote.votersCount, totalVoters)}%</span>
<span className="answer-percent" dir={lang.isRtl ? 'auto' : undefined}>
{getPercentage(answerVote.votersCount, totalVoters)}%
</span>
</div>
</div>
);

View File

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

View File

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

View File

@ -99,6 +99,20 @@
display: flex;
flex-direction: column;
justify-content: flex-end;
&:dir(rtl) {
.status {
text-align: initial;
unicode-bidi: plaintext;
}
}
&[dir=rtl] {
.status {
text-align: initial;
unicode-bidi: plaintext;
}
}
}
.title {
@ -130,4 +144,18 @@
font-size: 0.875rem;
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);
return (
<div className="ProfileInfo">
<div className="ProfileInfo" dir={lang.isRtl ? 'rtl' : undefined}>
<div className="photo-wrapper">
{renderPhotoTabs()}
<Transition activeKey={currentPhotoIndex} name={slideAnimation} className="profile-slide-container">
@ -200,7 +200,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
)}
</div>
<div className="info">
<div className="info" dir={lang.isRtl ? 'rtl' : 'auto'}>
{isSavedMessages ? (
<div className="title">
<h3 dir="auto">{lang('SavedMessages')}</h3>
@ -223,9 +223,7 @@ export default memo(withGlobal<OwnProps>(
const user = selectUser(global, userId);
const chat = selectChat(global, userId);
const isSavedMessages = !forceShowSelf && user && user.isSelf;
const {
animationLevel,
} = global.settings.byKey;
const { animationLevel } = global.settings.byKey;
return {
lastSyncTime, user, chat, isSavedMessages, animationLevel,

View File

@ -104,7 +104,10 @@ const RightSearch: FC<OwnProps & StateProps & DispatchProps> = ({
const text = getMessageSummaryText(lang, message);
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} />
<div className="info">
<div className="title">

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