More RTL support (#1071)
This commit is contained in:
parent
e54b877cf9
commit
45a3064153
@ -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 {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -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" />}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 && (
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -20,6 +20,7 @@ const UnpinAllMessagesModal: FC<OwnProps> = ({
|
||||
onUnpin,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -287,6 +287,7 @@ function processEntity(
|
||||
<a
|
||||
href={`tel:${entityText}`}
|
||||
className="text-entity-link"
|
||||
dir="auto"
|
||||
>
|
||||
{renderMessagePart(renderedContent)}
|
||||
</a>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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}
|
||||
>
|
||||
|
||||
@ -57,6 +57,11 @@
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
[dir=rtl] .archived-badge {
|
||||
margin-left: 0;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.Menu .bubble {
|
||||
min-width: 17rem;
|
||||
}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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')}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -38,6 +38,7 @@ const SettingsLanguage: FC<StateProps & DispatchProps> = ({
|
||||
|
||||
setLanguage(langCode, () => {
|
||||
unmarkIsLoading();
|
||||
|
||||
setSettingOption({ language: langCode });
|
||||
});
|
||||
}, [markIsLoading, unmarkIsLoading, setSettingOption]);
|
||||
|
||||
@ -52,7 +52,7 @@ const SettingsMain: FC<OwnProps & StateProps> = ({
|
||||
icon="settings"
|
||||
onClick={() => onScreenSelect(SettingsScreens.General)}
|
||||
>
|
||||
{lang('GeneralSettings')}
|
||||
{lang('Telegram.GeneralSettingsViewController')}
|
||||
</ListItem>
|
||||
<ListItem
|
||||
icon="unmute"
|
||||
|
||||
@ -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')}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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',
|
||||
]),
|
||||
|
||||
@ -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>
|
||||
) : (
|
||||
|
||||
@ -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">−{blockCount}</span>}
|
||||
{blockCount > 0 && <span className="date" dir="auto">−{blockCount}</span>}
|
||||
<span className="title">{lang('NeverShareWith')}</span>
|
||||
<span className="subtitle">{lang('EditAdminAddUsers')}</span>
|
||||
</div>
|
||||
|
||||
@ -22,4 +22,11 @@
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
&[dir=rtl] {
|
||||
.StickerButton,
|
||||
.Button {
|
||||
margin: 0 0 0 .5rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,6 +45,7 @@ const SettingsStickerSet: FC<OwnProps> = ({
|
||||
<Button
|
||||
ariaLabel={stickerSet.title}
|
||||
color="translucent"
|
||||
isRtl={lang.isRtl}
|
||||
>
|
||||
{stickerSet.isAnimated ? (
|
||||
<StickerSetCoverAnimated
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>
|
||||
</>
|
||||
)}
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 = () => {
|
||||
|
||||
@ -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 (
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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}
|
||||
/>
|
||||
)}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
}
|
||||
|
||||
.Avatar {
|
||||
margin-right: 1rem;
|
||||
margin-inline-end: 1rem;
|
||||
|
||||
@media (max-width: 600px) {
|
||||
display: none;
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
|
||||
|
||||
@ -339,11 +339,13 @@ const MiddleHeader: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
withFullInfo={isChatWithBot}
|
||||
withMediaViewer
|
||||
withUpdatingStatus
|
||||
noRtl
|
||||
/>
|
||||
) : (
|
||||
<GroupChatInfo
|
||||
chatId={chatId}
|
||||
typingStatus={typingStatus}
|
||||
noRtl
|
||||
withMediaViewer
|
||||
withFullInfo
|
||||
withUpdatingStatus
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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 && (
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -286,6 +286,11 @@
|
||||
@media (max-width: 600px) {
|
||||
bottom: 0.6875rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&[dir=rtl] .placeholder-text {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.text-entity-link {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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" />
|
||||
|
||||
@ -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} />
|
||||
|
||||
@ -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>
|
||||
)}
|
||||
|
||||
@ -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">
|
||||
|
||||
@ -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>(
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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')}>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -18,6 +18,8 @@
|
||||
h3 {
|
||||
margin-bottom: 0;
|
||||
margin-left: 1.5rem;
|
||||
unicode-bidi: plaintext;
|
||||
text-align: initial;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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')}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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) => (
|
||||
|
||||
@ -32,6 +32,12 @@
|
||||
.Switcher {
|
||||
margin-left: auto;
|
||||
}
|
||||
[dir=rtl] {
|
||||
.Switcher {
|
||||
margin-left: 0;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user