Add RTL Support (#1135)

This commit is contained in:
Alexander Zinchuk 2021-06-11 01:19:08 +03:00
parent 32c7b083c8
commit b2242f7fe4
63 changed files with 244 additions and 100 deletions

View File

@ -74,6 +74,7 @@
align-self: center; align-self: center;
min-width: 0; min-width: 0;
flex-grow: 1; flex-grow: 1;
text-align: initial;
} }
.content-row { .content-row {

View File

@ -237,7 +237,7 @@ const Audio: FC<OwnProps & StateProps> = ({
<> <>
<div className={contentClassName}> <div className={contentClassName}>
<div className="content-row"> <div className="content-row">
<p className="title">{renderText(getFirstLine())}</p> <p className="title" dir="auto">{renderText(getFirstLine())}</p>
<div className="message-date"> <div className="message-date">
{date && ( {date && (
@ -253,7 +253,7 @@ const Audio: FC<OwnProps & StateProps> = ({
{showSeekline && renderSeekline(playProgress, bufferedProgress, seekHandlers)} {showSeekline && renderSeekline(playProgress, bufferedProgress, seekHandlers)}
{!showSeekline && ( {!showSeekline && (
<p className="duration"> <p className="duration" dir="auto">
{playProgress > 0 ? `${formatMediaDuration(duration * playProgress)} / ` : undefined} {playProgress > 0 ? `${formatMediaDuration(duration * playProgress)} / ` : undefined}
{getSecondLine()} {getSecondLine()}
</p> </p>
@ -318,10 +318,10 @@ function renderAudio(
return ( return (
<div className="content"> <div className="content">
<p className="title">{renderText(title || fileName)}</p> <p className="title" dir="auto">{renderText(title || fileName)}</p>
{showSeekline && renderSeekline(playProgress, bufferedProgress, seekHandlers)} {showSeekline && renderSeekline(playProgress, bufferedProgress, seekHandlers)}
{!showSeekline && ( {!showSeekline && (
<div className="meta"> <div className="meta" dir="auto">
<span className="performer">{renderText(performer || 'Unknown')}</span> <span className="performer">{renderText(performer || 'Unknown')}</span>
{date && ( {date && (
<> <>
@ -333,7 +333,7 @@ function renderAudio(
)} )}
</div> </div>
)} )}
<p className="duration"> <p className="duration" dir="auto">
{playProgress > 0 ? `${formatMediaDuration(duration * playProgress)} / ` : undefined} {playProgress > 0 ? `${formatMediaDuration(duration * playProgress)} / ` : undefined}
{formatMediaDuration(duration)} {formatMediaDuration(duration)}
</p> </p>
@ -345,7 +345,7 @@ function renderVoice(voice: ApiVoice, renderedWaveform: any, isMediaUnread?: boo
return ( return (
<div className="content"> <div className="content">
{renderedWaveform} {renderedWaveform}
<p className="voice-duration"> <p className="voice-duration" dir="auto">
{formatMediaDuration(voice.duration)} {formatMediaDuration(voice.duration)}
{isMediaUnread && <span>&bull;</span>} {isMediaUnread && <span>&bull;</span>}
</p> </p>

View File

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

View File

@ -113,7 +113,7 @@ const File: FC<OwnProps> = ({
) : ( ) : (
<div className={`file-icon ${color}`}> <div className={`file-icon ${color}`}>
{extension.length <= 4 && ( {extension.length <= 4 && (
<span className="file-ext">{extension}</span> <span className="file-ext" dir="auto">{extension}</span>
)} )}
</div> </div>
)} )}
@ -129,8 +129,8 @@ const File: FC<OwnProps> = ({
{onClick && <i className={buildClassName('icon-download', shouldSpinnerRender && 'hidden')} />} {onClick && <i className={buildClassName('icon-download', shouldSpinnerRender && 'hidden')} />}
</div> </div>
<div className="file-info"> <div className="file-info">
<div className="file-title">{renderText(name)}</div> <div className="file-title" dir="auto">{renderText(name)}</div>
<div className="file-subtitle"> <div className="file-subtitle" dir="auto">
<span> <span>
{isTransferring && transferProgress ? `${Math.round(transferProgress * 100)}%` : sizeString} {isTransferring && transferProgress ? `${Math.round(transferProgress * 100)}%` : sizeString}
</span> </span>

View File

@ -84,7 +84,7 @@ const GroupChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
function renderStatusOrTyping() { function renderStatusOrTyping() {
if (withUpdatingStatus && !areMessagesLoaded && !isRestricted) { if (withUpdatingStatus && !areMessagesLoaded && !isRestricted) {
return ( return (
<span className="status">{lang('Updating')}</span> <span className="status" dir="auto">{lang('Updating')}</span>
); );
} }
@ -98,7 +98,7 @@ const GroupChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
if (withChatType) { if (withChatType) {
return ( return (
<div className="status">{lang(getChatTypeString(chat))}</div> <div className="status" dir="auto">{lang(getChatTypeString(chat))}</div>
); );
} }
@ -125,7 +125,7 @@ const GroupChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
/> />
<div className="info"> <div className="info">
<div className="title"> <div className="title">
<h3>{renderText(getChatTitle(lang, chat))}</h3> <h3 dir="auto">{renderText(getChatTitle(lang, chat))}</h3>
{chat.isVerified && <VerifiedIcon />} {chat.isVerified && <VerifiedIcon />}
</div> </div>
{renderStatusOrTyping()} {renderStatusOrTyping()}

View File

@ -85,7 +85,7 @@ const PickerSelectedItem: FC<OwnProps & StateProps> = ({
> >
{iconElement} {iconElement}
{!isMinimized && ( {!isMinimized && (
<div className="item-name"> <div className="item-name" dir="auto">
{titleText} {titleText}
</div> </div>
)} )}

View File

@ -83,13 +83,13 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
function renderStatusOrTyping() { function renderStatusOrTyping() {
if (status) { if (status) {
return ( return (
<span className="status">{status}</span> <span className="status" dir="auto">{status}</span>
); );
} }
if (withUpdatingStatus && !areMessagesLoaded) { if (withUpdatingStatus && !areMessagesLoaded) {
return ( return (
<span className="status">{lang('Updating')}</span> <span className="status" dir="auto">{lang('Updating')}</span>
); );
} }
@ -104,7 +104,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
return ( return (
<div className={`status ${isUserOnline(user) ? 'online' : ''}`}> <div className={`status ${isUserOnline(user) ? 'online' : ''}`}>
{withUsername && user.username && <span className="handle">{user.username}</span>} {withUsername && user.username && <span className="handle">{user.username}</span>}
<span className="user-status">{getUserStatus(lang, user)}</span> <span className="user-status" dir="auto">{getUserStatus(lang, user)}</span>
</div> </div>
); );
} }
@ -125,7 +125,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
</div> </div>
) : ( ) : (
<div className="title"> <div className="title">
<h3>{fullName && renderText(fullName)}</h3> <h3 dir="auto">{fullName && renderText(fullName)}</h3>
{user && user.isVerified && <VerifiedIcon />} {user && user.isVerified && <VerifiedIcon />}
</div> </div>
)} )}

View File

@ -23,7 +23,7 @@ const TypingStatus: FC<OwnProps & StateProps> = ({ typingStatus, typingUser }) =
return ( return (
<p className="typing-status"> <p className="typing-status">
{typingUserName && ( {typingUserName && (
<span className="sender-name">{renderText(typingUserName)}</span> <span className="sender-name" dir="auto">{renderText(typingUserName)}</span>
)} )}
{typingStatus.action} {typingStatus.action}
<span className="ellipsis" /> <span className="ellipsis" />

View File

@ -229,6 +229,7 @@ function processEntity(
<a <a
onClick={handleBotCommandClick} onClick={handleBotCommandClick}
className="text-entity-link" className="text-entity-link"
dir="auto"
> >
{renderMessagePart(renderedContent)} {renderMessagePart(renderedContent)}
</a> </a>
@ -238,6 +239,7 @@ function processEntity(
<a <a
onClick={handleHashtagClick} onClick={handleHashtagClick}
className="text-entity-link" className="text-entity-link"
dir="auto"
> >
{renderMessagePart(renderedContent)} {renderMessagePart(renderedContent)}
</a> </a>
@ -247,6 +249,7 @@ function processEntity(
<a <a
onClick={handleHashtagClick} onClick={handleHashtagClick}
className="text-entity-link" className="text-entity-link"
dir="auto"
> >
{renderMessagePart(renderedContent)} {renderMessagePart(renderedContent)}
</a> </a>
@ -260,6 +263,7 @@ function processEntity(
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
className="text-entity-link" className="text-entity-link"
dir="auto"
> >
{renderMessagePart(renderedContent)} {renderMessagePart(renderedContent)}
</a> </a>
@ -385,6 +389,7 @@ function processEntityAsHtml(
data-entity-type="${ApiMessageEntityTypes.MentionName}" data-entity-type="${ApiMessageEntityTypes.MentionName}"
data-user-id="${entity.userId}" data-user-id="${entity.userId}"
contenteditable="false" contenteditable="false"
dir="auto"
>${renderedContent}</a>`; >${renderedContent}</a>`;
case ApiMessageEntityTypes.Url: case ApiMessageEntityTypes.Url:
case ApiMessageEntityTypes.TextUrl: case ApiMessageEntityTypes.TextUrl:
@ -392,6 +397,7 @@ function processEntityAsHtml(
class="text-entity-link" class="text-entity-link"
href=${getLinkUrl(rawEntityText, entity)} href=${getLinkUrl(rawEntityText, entity)}
data-entity-type="${entity.type}" data-entity-type="${entity.type}"
dir="auto"
>${renderedContent}</a>`; >${renderedContent}</a>`;
default: default:
return renderedContent; return renderedContent;

View File

@ -82,6 +82,7 @@
padding-right: 0.25rem; padding-right: 0.25rem;
flex-grow: 1; flex-grow: 1;
color: var(--color-text-secondary); color: var(--color-text-secondary);
unicode-bidi: plaintext;
.sender-name { .sender-name {
color: var(--color-text); color: var(--color-text);

View File

@ -193,7 +193,7 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
if (draft && draft.text.length) { if (draft && draft.text.length) {
return ( return (
<p className="last-message"> <p className="last-message" dir="auto">
<span className="draft">{lang('Draft')}</span> <span className="draft">{lang('Draft')}</span>
{renderText(draft.text)} {renderText(draft.text)}
</p> </p>
@ -210,7 +210,7 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
: lastMessageSender; : lastMessageSender;
return ( return (
<p className="last-message"> <p className="last-message" dir="auto">
{renderText(renderActionMessageText( {renderText(renderActionMessageText(
lang, lang,
lastMessage, lastMessage,
@ -227,7 +227,7 @@ const Chat: FC<OwnProps & StateProps & DispatchProps> = ({
const senderName = getMessageSenderName(lang, chatId, lastMessageSender); const senderName = getMessageSenderName(lang, chatId, lastMessageSender);
return ( return (
<p className="last-message"> <p className="last-message" dir="auto">
{senderName && ( {senderName && (
<span className="sender-name">{renderText(senderName)}</span> <span className="sender-name">{renderText(senderName)}</span>
)} )}

View File

@ -82,7 +82,7 @@ const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
</ListItem> </ListItem>
)) ))
) : viewportIds && !viewportIds.length ? ( ) : viewportIds && !viewportIds.length ? (
<p className="no-results" key="no-results"> <p className="no-results" key="no-results" dir="auto">
{filter.length ? 'No contacts matched your search.' : 'Contact list is empty.'} {filter.length ? 'No contacts matched your search.' : 'Contact list is empty.'}
</p> </p>
) : ( ) : (

View File

@ -86,7 +86,7 @@ const ChatMessage: FC<OwnProps & StateProps & DispatchProps> = ({
<div className="info"> <div className="info">
<div className="info-row"> <div className="info-row">
<div className="title"> <div className="title">
<h3>{renderText(getChatTitle(lang, chat, privateChatUser))}</h3> <h3 dir="auto">{renderText(getChatTitle(lang, chat, privateChatUser))}</h3>
{chat.isVerified && <VerifiedIcon />} {chat.isVerified && <VerifiedIcon />}
</div> </div>
<div className="message-date"> <div className="message-date">
@ -97,7 +97,7 @@ const ChatMessage: FC<OwnProps & StateProps & DispatchProps> = ({
</div> </div>
<div className="subtitle"> <div className="subtitle">
<div className="message"> <div className="message" dir="auto">
{renderMessageSummary(lang, message, mediaBlobUrl || mediaThumbnail, searchQuery, isRoundVideo)} {renderMessageSummary(lang, message, mediaBlobUrl || mediaThumbnail, searchQuery, isRoundVideo)}
</div> </div>
</div> </div>

View File

@ -95,15 +95,24 @@
.ListItem.search-result { .ListItem.search-result {
.ChatInfo { .ChatInfo {
.handle { .handle {
unicode-bidi: plaintext;
color: var(--color-primary); color: var(--color-primary);
&::before { &::before {
content: '@'; content: '@';
html[lang=ar] & {
content: ' ،@';
margin-inline-end: .25rem;
}
} }
&::after { &::after {
content: ', '; content: ', ';
color: var(--color-text-secondary); color: var(--color-text-secondary);
html[lang=ar] & {
content: '';
}
} }
} }
} }

View File

@ -74,7 +74,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
<div className="multiline-menu-item"> <div className="multiline-menu-item">
<span className="title">{lang('BlockedUsers')}</span> <span className="title">{lang('BlockedUsers')}</span>
{blockedCount > 0 && ( {blockedCount > 0 && (
<span className="subtitle"> <span className="subtitle" dir="auto">
{lang('Users', blockedCount)} {lang('Users', blockedCount)}
</span> </span>
)} )}
@ -89,7 +89,9 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
> >
<div className="multiline-menu-item"> <div className="multiline-menu-item">
<span className="title">{lang('TwoStepVerification')}</span> <span className="title">{lang('TwoStepVerification')}</span>
<span className="subtitle">{lang(hasPassword ? 'PasswordOn' : 'PasswordOff')}</span> <span className="subtitle" dir="auto">
{lang(hasPassword ? 'PasswordOn' : 'PasswordOff')}
</span>
</div> </div>
</ListItem> </ListItem>
<ListItem <ListItem
@ -100,7 +102,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
<div className="multiline-menu-item"> <div className="multiline-menu-item">
<span className="title">{lang('SessionsTitle')}</span> <span className="title">{lang('SessionsTitle')}</span>
{sessionsCount > 0 && ( {sessionsCount > 0 && (
<span className="subtitle"> <span className="subtitle" dir="auto">
{sessionsCount === 1 ? '1 session' : `${sessionsCount} sessions`} {sessionsCount === 1 ? '1 session' : `${sessionsCount} sessions`}
</span> </span>
)} )}
@ -117,7 +119,9 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
> >
<div className="multiline-menu-item"> <div className="multiline-menu-item">
<span className="title">{lang('PrivacyPhoneTitle')}</span> <span className="title">{lang('PrivacyPhoneTitle')}</span>
<span className="subtitle">{getVisibilityValue(visibilityPrivacyPhoneNumber)}</span> <span className="subtitle" dir="auto">
{getVisibilityValue(visibilityPrivacyPhoneNumber)}
</span>
</div> </div>
</ListItem> </ListItem>
<ListItem <ListItem
@ -126,7 +130,9 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
> >
<div className="multiline-menu-item"> <div className="multiline-menu-item">
<span className="title">{lang('LastSeenTitle')}</span> <span className="title">{lang('LastSeenTitle')}</span>
<span className="subtitle">{getVisibilityValue(visibilityPrivacyLastSeen)}</span> <span className="subtitle" dir="auto">
{getVisibilityValue(visibilityPrivacyLastSeen)}
</span>
</div> </div>
</ListItem> </ListItem>
<ListItem <ListItem
@ -135,7 +141,9 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
> >
<div className="multiline-menu-item"> <div className="multiline-menu-item">
<span className="title">{lang('PrivacyProfilePhotoTitle')}</span> <span className="title">{lang('PrivacyProfilePhotoTitle')}</span>
<span className="subtitle">{getVisibilityValue(visibilityPrivacyProfilePhoto)}</span> <span className="subtitle" dir="auto">
{getVisibilityValue(visibilityPrivacyProfilePhoto)}
</span>
</div> </div>
</ListItem> </ListItem>
<ListItem <ListItem
@ -144,7 +152,9 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
> >
<div className="multiline-menu-item"> <div className="multiline-menu-item">
<span className="title">{lang('PrivacyForwardsTitle')}</span> <span className="title">{lang('PrivacyForwardsTitle')}</span>
<span className="subtitle">{getVisibilityValue(visibilityPrivacyForwarding)}</span> <span className="subtitle" dir="auto">
{getVisibilityValue(visibilityPrivacyForwarding)}
</span>
</div> </div>
</ListItem> </ListItem>
<ListItem <ListItem
@ -153,7 +163,9 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
> >
<div className="multiline-menu-item"> <div className="multiline-menu-item">
<span className="title">{lang('WhoCanAddMe')}</span> <span className="title">{lang('WhoCanAddMe')}</span>
<span className="subtitle">{getVisibilityValue(visibilityPrivacyGroupChats)}</span> <span className="subtitle" dir="auto">
{getVisibilityValue(visibilityPrivacyGroupChats)}
</span>
</div> </div>
</ListItem> </ListItem>
</div> </div>

View File

@ -67,13 +67,13 @@ const SettingsPrivacyBlockedUsers: FC<StateProps & DispatchProps> = ({
style={`top: ${(viewportOffset + i) * CHAT_HEIGHT_PX}px;`} style={`top: ${(viewportOffset + i) * CHAT_HEIGHT_PX}px;`}
> >
<Avatar size="medium" user={user} chat={chat} /> <Avatar size="medium" user={user} chat={chat} />
<div className="contact-info"> <div className="contact-info" dir="auto">
<h3>{renderText((isPrivate ? getUserFullName(user) : getChatTitle(lang, chat!)) || '')}</h3> <h3 dir="auto">{renderText((isPrivate ? getUserFullName(user) : getChatTitle(lang, chat!)) || '')}</h3>
{user && user.phoneNumber && ( {user && user.phoneNumber && (
<div className="contact-phone">{formatPhoneNumberWithCode(user.phoneNumber)}</div> <div className="contact-phone" dir="auto">{formatPhoneNumberWithCode(user.phoneNumber)}</div>
)} )}
{user && !user.phoneNumber && user.username && ( {user && !user.phoneNumber && user.username && (
<div className="contact-username">@{user.username}</div> <div className="contact-username" dir="auto">@{user.username}</div>
)} )}
</div> </div>
</ListItem> </ListItem>

View File

@ -121,7 +121,7 @@ const SettingsFoldersChatsPicker: FC<OwnProps> = ({
ripple ripple
> >
<i className={`icon-${type.icon}`} /> <i className={`icon-${type.icon}`} />
<h3 className="chat-type">{lang(type.title)}</h3> <h3 className="chat-type" dir="auto">{lang(type.title)}</h3>
<Checkbox <Checkbox
label="" label=""
checked={selectedChatTypes.includes(type.key)} checked={selectedChatTypes.includes(type.key)}
@ -179,7 +179,9 @@ const SettingsFoldersChatsPicker: FC<OwnProps> = ({
placeholder={lang('Search')} placeholder={lang('Search')}
/> />
) : ( ) : (
<p className="max-items-reached">{`Sorry, you can't add more than ${MAX_CHATS} chats.`}</p> <p className="max-items-reached" dir="auto">
{`Sorry, you can't add more than ${MAX_CHATS} chats.`}
</p>
)} )}
</div> </div>
<InfiniteScroll <InfiniteScroll

View File

@ -139,7 +139,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps & DispatchProps> = ({
)} )}
</div> </div>
<p className="settings-item-description mb-3"> <p className="settings-item-description mb-3" dir="auto">
{lang('CreateNewFilterInfo')} {lang('CreateNewFilterInfo')}
</p> </p>

View File

@ -32,7 +32,7 @@ const SettingsTwoFaCongratulations: FC<OwnProps & StateProps> = ({
<div className="settings-content-header"> <div className="settings-content-header">
<AnimatedEmoji sticker={animatedEmoji} /> <AnimatedEmoji sticker={animatedEmoji} />
<p className="settings-item-description mb-3"> <p className="settings-item-description mb-3" dir="auto">
{lang('TwoStepVerificationPasswordSetInfo')} {lang('TwoStepVerificationPasswordSetInfo')}
</p> </p>
</div> </div>

View File

@ -29,7 +29,7 @@ const SettingsTwoFaEnabled: FC<OwnProps & StateProps> = ({
<div className="settings-content-header"> <div className="settings-content-header">
<AnimatedEmoji sticker={animatedEmoji} /> <AnimatedEmoji sticker={animatedEmoji} />
<p className="settings-item-description mb-3"> <p className="settings-item-description mb-3" dir="auto">
{renderText(lang('EnabledPasswordText'), ['br'])} {renderText(lang('EnabledPasswordText'), ['br'])}
</p> </p>
</div> </div>

View File

@ -25,7 +25,7 @@ const SettingsTwoFaStart: FC<OwnProps & StateProps> = ({ animatedEmoji, onStart
<div className="settings-content-header"> <div className="settings-content-header">
<AnimatedEmoji sticker={animatedEmoji} /> <AnimatedEmoji sticker={animatedEmoji} />
<p className="settings-item-description mb-3"> <p className="settings-item-description mb-3" dir="auto">
{lang('SetAdditionalPasswordInfo')} {lang('SetAdditionalPasswordInfo')}
</p> </p>
</div> </div>

View File

@ -31,6 +31,7 @@
padding: 0.5rem; padding: 0.5rem;
font-size: 1.25rem; font-size: 1.25rem;
line-height: 1.75rem; line-height: 1.75rem;
unicode-bidi: plaintext;
} }
} }

View File

@ -46,7 +46,7 @@ const MediaViewerFooter: FC<OwnProps> = ({ text = '', isHideable, onClick }) =>
<div className={`MediaViewerFooter ${isHideable ? 'hideable' : ''}`} onClick={stopEvent}> <div className={`MediaViewerFooter ${isHideable ? 'hideable' : ''}`} onClick={stopEvent}>
{text && ( {text && (
<div className="media-viewer-footer-content" onClick={onClick}> <div className="media-viewer-footer-content" onClick={onClick}>
<p className={`media-text custom-scroll ${isMultiline ? 'multiline' : ''}`}>{text}</p> <p className={`media-text custom-scroll ${isMultiline ? 'multiline' : ''}`} dir="auto">{text}</p>
</div> </div>
)} )}
</div> </div>

View File

@ -58,10 +58,10 @@ const SenderInfo: FC<OwnProps & StateProps & DispatchProps> = ({
<Avatar key={sender.id} size="medium" user={sender as ApiUser} /> <Avatar key={sender.id} size="medium" user={sender as ApiUser} />
)} )}
<div className="meta"> <div className="meta">
<div className="title"> <div className="title" dir="auto">
{senderTitle && renderText(senderTitle)} {senderTitle && renderText(senderTitle)}
</div> </div>
<div className="date"> <div className="date" dir="auto">
{isAvatar ? lang('lng_mediaview_profile_photo') : formatMediaDateTime(lang, message!.date * 1000)} {isAvatar ? lang('lng_mediaview_profile_photo') : formatMediaDateTime(lang, message!.date * 1000)}
</div> </div>
</div> </div>

View File

@ -102,9 +102,9 @@ function renderAudio(audio: ApiAudio) {
return ( return (
<> <>
<div className="title">{renderText(title || fileName)}</div> <div className="title" dir="auto">{renderText(title || fileName)}</div>
{performer && ( {performer && (
<div className="subtitle">{renderText(performer)}</div> <div className="subtitle" dir="auto">{renderText(performer)}</div>
)} )}
</> </>
); );
@ -113,8 +113,8 @@ function renderAudio(audio: ApiAudio) {
function renderVoice(subtitle: string, senderName?: string) { function renderVoice(subtitle: string, senderName?: string) {
return ( return (
<> <>
<div className="title">{senderName && renderText(senderName)}</div> <div className="title" dir="auto">{senderName && renderText(senderName)}</div>
<div className="subtitle">{subtitle}</div> <div className="subtitle" dir="auto">{subtitle}</div>
</> </>
); );
} }

View File

@ -86,10 +86,10 @@ const HeaderPinnedMessage: FC<OwnProps> = ({
/> />
{mediaThumbnail && renderPictogram(mediaThumbnail, mediaBlobUrl)} {mediaThumbnail && renderPictogram(mediaThumbnail, mediaBlobUrl)}
<div className="message-text"> <div className="message-text">
<div className="title"> <div className="title" dir="auto">
{customTitle || `${lang('PinnedMessage')} ${index > 0 ? `#${count - index}` : ''}`} {customTitle || `${lang('PinnedMessage')} ${index > 0 ? `#${count - index}` : ''}`}
</div> </div>
<p>{renderText(text)}</p> <p dir="auto">{renderText(text)}</p>
</div> </div>
<RippleEffect /> <RippleEffect />

View File

@ -702,7 +702,7 @@ function renderMessages(
teactFastList teactFastList
> >
<div className="sticky-date" key="date-header"> <div className="sticky-date" key="date-header">
<span> <span dir="auto">
{isSchedule && dateGroup.originalDate === SCHEDULED_WHEN_ONLINE && ( {isSchedule && dateGroup.originalDate === SCHEDULED_WHEN_ONLINE && (
lang('MessageScheduledUntilOnline') lang('MessageScheduledUntilOnline')
)} )}

View File

@ -226,6 +226,7 @@
margin: 0; margin: 0;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
unicode-bidi: plaintext;
@media (max-width: 600px) { @media (max-width: 600px) {
display: block; display: block;
@ -264,6 +265,10 @@
margin-top: 0.05rem; margin-top: 0.05rem;
} }
} }
.user-status, .status {
unicode-bidi: plaintext;
}
} }
.Avatar { .Avatar {

View File

@ -256,6 +256,8 @@
overflow: hidden; overflow: hidden;
line-height: 1.375rem; line-height: 1.375rem;
font-family: Roboto, -apple-system, "Apple Color Emoji", "Helvetica Neue", sans-serif; font-family: Roboto, -apple-system, "Apple Color Emoji", "Helvetica Neue", sans-serif;
unicode-bidi: plaintext;
text-align: initial;
&.overflown { &.overflown {
overflow-y: auto; overflow-y: auto;
@ -278,6 +280,8 @@
bottom: .9375rem; bottom: .9375rem;
color: var(--color-placeholders); color: var(--color-placeholders);
pointer-events: none; pointer-events: none;
unicode-bidi: plaintext;
text-align: initial;
@media (max-width: 600px) { @media (max-width: 600px) {
bottom: 0.6875rem; bottom: 0.6875rem;
@ -301,6 +305,8 @@
opacity: 0; opacity: 0;
pointer-events: none; pointer-events: none;
z-index: -10; z-index: -10;
unicode-bidi: plaintext;
text-align: initial;
} }
} }

View File

@ -47,7 +47,9 @@ const EmojiCategory: FC<OwnProps> = ({
id={`emoji-category-${index}`} id={`emoji-category-${index}`}
className="symbol-set" className="symbol-set"
> >
<p className="symbol-set-name">{lang(category.id === 'recent' ? 'RecentStickers' : `Emoji${index}`)}</p> <p className="symbol-set-name" dir="auto">
{lang(category.id === 'recent' ? 'RecentStickers' : `Emoji${index}`)}
</p>
<div <div
className={buildClassName('symbol-set-container', transitionClassNames)} className={buildClassName('symbol-set-container', transitionClassNames)}
// @ts-ignore // @ts-ignore

View File

@ -356,6 +356,7 @@ const MessageInput: FC<OwnProps & StateProps & DispatchProps> = ({
ref={inputRef} ref={inputRef}
id={editableInputId || EDITABLE_INPUT_ID} id={editableInputId || EDITABLE_INPUT_ID}
className={className} className={className}
dir="auto"
contentEditable contentEditable
onClick={focusInput} onClick={focusInput}
onChange={handleChange} onChange={handleChange}
@ -364,8 +365,8 @@ const MessageInput: FC<OwnProps & StateProps & DispatchProps> = ({
onContextMenu={stopEvent} onContextMenu={stopEvent}
onTouchCancel={handleTouchSelection} onTouchCancel={handleTouchSelection}
/> />
<div ref={cloneRef} className={buildClassName(className, 'clone')} /> <div ref={cloneRef} className={buildClassName(className, 'clone')} dir="auto" />
<span className="placeholder-text">{placeholder}</span> <span className="placeholder-text" dir="auto">{placeholder}</span>
<TextFormatter <TextFormatter
isOpen={isTextFormatterOpen} isOpen={isTextFormatterOpen}
anchorPosition={textFormatterAnchorPosition} anchorPosition={textFormatterAnchorPosition}

View File

@ -44,6 +44,10 @@
.option-wrapper { .option-wrapper {
position: relative; position: relative;
.form-control {
padding-right: 3rem;
}
.option-remove-button { .option-remove-button {
position: absolute; position: absolute;
top: 0.3125rem; top: 0.3125rem;

View File

@ -337,6 +337,7 @@ const PollModal: FC<OwnProps> = ({ isOpen, onSend, onClear }) => {
ref={solutionRef} ref={solutionRef}
className="form-control" className="form-control"
contentEditable contentEditable
dir="auto"
onChange={(e) => setSolution(e.currentTarget.innerHTML)} onChange={(e) => setSolution(e.currentTarget.innerHTML)}
/> />
<div className="note">{lang('CreatePoll.ExplanationInfo')}</div> <div className="note">{lang('CreatePoll.ExplanationInfo')}</div>

View File

@ -122,6 +122,12 @@
overflow: hidden; overflow: hidden;
white-space: nowrap; white-space: nowrap;
text-overflow: ellipsis; text-overflow: ellipsis;
text-align: initial;
unicode-bidi: plaintext;
}
&-container {
text-align: initial;
} }
&-button { &-button {

View File

@ -258,7 +258,7 @@ const TextFormatter: FC<OwnProps> = ({
} }
const text = getSelectedText(); const text = getSelectedText();
document.execCommand('insertHTML', false, `<code class="text-entity-code">${text}</code>`); document.execCommand('insertHTML', false, `<code class="text-entity-code" dir="auto">${text}</code>`);
onClose(); onClose();
}, [ }, [
getSelectedElement, getSelectedText, onClose, getSelectedElement, getSelectedText, onClose,
@ -282,7 +282,11 @@ const TextFormatter: FC<OwnProps> = ({
const text = getSelectedText(); const text = getSelectedText();
restoreSelection(); restoreSelection();
document.execCommand('insertHTML', false, `<a href=${formattedLinkUrl} class="text-entity-link">${text}</a>`); document.execCommand(
'insertHTML',
false,
`<a href=${formattedLinkUrl} class="text-entity-link" dir="auto">${text}</a>`,
);
onClose(); onClose();
} }
@ -424,6 +428,7 @@ const TextFormatter: FC<OwnProps> = ({
placeholder="Enter URL..." placeholder="Enter URL..."
autoComplete="off" autoComplete="off"
inputMode="url" inputMode="url"
dir="auto"
onChange={handleLinkUrlChange} onChange={handleLinkUrlChange}
onScroll={updateInputStyles} onScroll={updateInputStyles}
/> />

View File

@ -73,6 +73,7 @@ export default function useMentionTooltip(
data-entity-type="${ApiMessageEntityTypes.MentionName}" data-entity-type="${ApiMessageEntityTypes.MentionName}"
data-user-id="${user.id}" data-user-id="${user.id}"
contenteditable="false" contenteditable="false"
dir="auto"
>${getUserFirstOrLastName(user)}</a>`; >${getUserFirstOrLastName(user)}</a>`;
const atIndex = html.lastIndexOf('@'); const atIndex = html.lastIndexOf('@');

View File

@ -78,7 +78,7 @@ const CommentButton: FC<OwnProps & StateProps & DispatchProps> = ({
<i className="icon-comments-sticker" /> <i className="icon-comments-sticker" />
{(!recentRepliers || recentRepliers.length === 0) && <i className="icon-comments" />} {(!recentRepliers || recentRepliers.length === 0) && <i className="icon-comments" />}
{renderRecentRepliers()} {renderRecentRepliers()}
<div className="label"> <div className="label" dir="auto">
{messagesCount ? lang('Comments', messagesCount, 'i') : lang('LeaveAComment')} {messagesCount ? lang('Comments', messagesCount, 'i') : lang('LeaveAComment')}
</div> </div>
<i className="icon-next" /> <i className="icon-next" />

View File

@ -35,7 +35,7 @@ const MentionLink: FC<OwnProps & StateProps & DispatchProps> = ({
}; };
return ( return (
<a onClick={handleClick} className="text-entity-link"> <a onClick={handleClick} className="text-entity-link" dir="auto">
{children} {children}
</a> </a>
); );

View File

@ -503,9 +503,10 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
noMediaCorners && 'no-media-corners', noMediaCorners && 'no-media-corners',
); );
const hasCustomAppendix = isLastInGroup && !textParts && !asForwarded && !hasThread; const hasCustomAppendix = isLastInGroup && !textParts && !asForwarded && !hasThread;
const shouldInlineMeta = !webPage && !animatedEmoji && textParts;
return ( return (
<div className={className} onDoubleClick={handleContentDoubleClick}> <div className={className} onDoubleClick={handleContentDoubleClick} dir="auto">
{renderSenderName()} {renderSenderName()}
{hasReply && ( {hasReply && (
<EmbeddedMessage <EmbeddedMessage
@ -605,7 +606,19 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
{poll && ( {poll && (
<Poll message={message} poll={poll} onSendVote={handleVoteSend} /> <Poll message={message} poll={poll} onSendVote={handleVoteSend} />
)} )}
{!animatedEmoji && textParts && <p className="text-content">{textParts}</p>} {!animatedEmoji && textParts && (
<p className={`text-content ${shouldInlineMeta ? 'with-meta' : ''}`} dir="auto">
{textParts}
{shouldInlineMeta && (
<MessageMeta
message={message}
outgoingStatus={outgoingStatus}
signature={signature}
onClick={handleMessageSelect}
/>
)}
</p>
)}
{webPage && ( {webPage && (
<WebPage <WebPage
message={message} message={message}
@ -737,13 +750,14 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
className={contentClassName} className={contentClassName}
// @ts-ignore // @ts-ignore
style={style} style={style}
dir="auto"
> >
{withAppendix && (<div className="svg-appendix" ref={appendixRef} />)} {withAppendix && (<div className="svg-appendix" ref={appendixRef} />)}
{asForwarded && !customShape && (!isInDocumentGroup || isFirstInDocumentGroup) && ( {asForwarded && !customShape && (!isInDocumentGroup || isFirstInDocumentGroup) && (
<div className="message-title">{lang('ForwardedMessage')}</div> <div className="message-title">{lang('ForwardedMessage')}</div>
)} )}
{renderContent()} {renderContent()}
{(!isInDocumentGroup || isLastInDocumentGroup) && ( {(!isInDocumentGroup || isLastInDocumentGroup) && !(!webPage && !animatedEmoji && textParts) && (
<MessageMeta <MessageMeta
message={message} message={message}
outgoingStatus={outgoingStatus} outgoingStatus={outgoingStatus}

View File

@ -6,7 +6,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
background: rgba(#999999, 0.6); background: rgba(#999999, 0.6);
border-radius: .65rem; border-radius: .625rem;
padding: 0 .25rem; padding: 0 .25rem;
color: white; color: white;
cursor: pointer; cursor: pointer;
@ -64,9 +64,21 @@
} }
} }
.media:not(.text):dir(rtl) &,
.Message .custom-shape:dir(rtl) & {
right: auto !important;
left: .25rem;
padding: 0 .375rem 0 .3125rem;
}
.is-forwarded.media:not(.text):dir(rtl) &,
.Message .is-forwarded.custom-shape:dir(rtl) & {
left: .8125rem;
}
.is-forwarded.media:not(.text) & { .is-forwarded.media:not(.text) & {
bottom: 0.9rem; bottom: 0.935rem;
right: 0.8rem; right: 0.8125rem;
} }
.emoji-only & { .emoji-only & {
@ -80,6 +92,8 @@
.MessageOutgoingStatus { .MessageOutgoingStatus {
margin-left: -.1875rem; margin-left: -.1875rem;
font-size: 1.1875rem; font-size: 1.1875rem;
border-radius: .625rem;
.Message.own & { .Message.own & {
color: var(--color-accent-own); color: var(--color-accent-own);
} }

View File

@ -1,5 +1,6 @@
.Poll { .Poll {
min-width: 15rem; min-width: 15rem;
text-align: initial;
@media (max-width: 600px) { @media (max-width: 600px) {
min-width: 50vw; min-width: 50vw;

View File

@ -256,7 +256,7 @@ const Poll: FC<OwnProps & StateProps & DispatchProps> = ({
} }
return ( return (
<div className="Poll"> <div className="Poll" dir="auto">
{renderSolution()} {renderSolution()}
<div className="poll-question">{renderText(summary.question)}</div> <div className="poll-question">{renderText(summary.question)}</div>
<div className="poll-type"> <div className="poll-type">

View File

@ -72,6 +72,7 @@ const WebPage: FC<OwnProps> = ({
<div <div
className={className} className={className}
data-initial={(siteName || displayUrl)[0]} data-initial={(siteName || displayUrl)[0]}
dir="auto"
> >
{photo && ( {photo && (
<Photo <Photo

View File

@ -10,6 +10,34 @@
margin: 0; margin: 0;
word-break: break-word; word-break: break-word;
line-height: 1.3125; line-height: 1.3125;
text-align: initial;
display: flow-root;
unicode-bidi: plaintext;
}
.text-entity-link {
unicode-bidi: plaintext;
}
.text-content,
&.document {
& > .MessageMeta {
position: relative;
top: .4375rem;
bottom: auto !important;
float: right;
line-height: 1;
margin-left: .4375rem;
margin-right: -.5rem;
}
&:dir(rtl) {
& > .MessageMeta {
float: left;
margin-left: -.25rem;
margin-right: .4375rem;
}
}
} }
.theme-dark .Message.own & { .theme-dark .Message.own & {
@ -24,6 +52,10 @@
&:not(.custom-shape) { &:not(.custom-shape) {
font-size: var(--message-text-size, 1rem); font-size: var(--message-text-size, 1rem);
& > .content-inner {
min-width: 0;
}
} }
.matching-text-highlight { .matching-text-highlight {
@ -40,6 +72,7 @@
font-weight: 500; font-weight: 500;
line-height: 1.25rem; line-height: 1.25rem;
color: var(--accent-color); color: var(--accent-color);
unicode-bidi: plaintext;
display: flex; display: flex;
& > .interactive { & > .interactive {
@ -94,8 +127,8 @@
.admin-title { .admin-title {
flex: 1; flex: 1;
margin-left: 1rem; margin-inline-start: 1rem;
text-align: right; text-align: end;
font-weight: 400; font-weight: 400;
font-size: 0.75rem; font-size: 0.75rem;
margin-top: -0.1rem; margin-top: -0.1rem;
@ -118,7 +151,7 @@
&.has-solid-background { &.has-solid-background {
padding: .3125rem .5rem .375rem; padding: .3125rem .5rem .375rem;
.text-content:last-child::after { .forwarded-message > .text-content:not(.with-meta):last-child::after {
content: ''; content: '';
display: inline-block; display: inline-block;
width: var(--meta-safe-area-size); width: var(--meta-safe-area-size);
@ -210,7 +243,7 @@
width: 1.25rem; width: 1.25rem;
background-size: 1.25rem; background-size: 1.25rem;
color: transparent; color: transparent;
margin-right: 1px; margin-inline-end: 1px;
vertical-align: text-bottom; vertical-align: text-bottom;
&::selection { &::selection {
@ -531,8 +564,9 @@
.text-entity-link { .text-entity-link {
color: var(--color-links) !important; color: var(--color-links) !important;
text-decoration: none; text-decoration: none;
word-break: none; word-break: break-word;
cursor: pointer; cursor: pointer;
unicode-bidi: initial;
&:hover, &:active, &:visited { &:hover, &:active, &:visited {
color: var(--color-links-hover) !important; color: var(--color-links-hover) !important;

View File

@ -86,7 +86,7 @@ const ChatExtra: FC<OwnProps & StateProps & DispatchProps> = ({
<div className="ChatExtra"> <div className="ChatExtra">
{formattedNumber && !!formattedNumber.length && ( {formattedNumber && !!formattedNumber.length && (
<ListItem icon="phone" multiline narrow ripple onClick={() => copy(formattedNumber, lang('Phone'))}> <ListItem icon="phone" multiline narrow ripple onClick={() => copy(formattedNumber, lang('Phone'))}>
<span className="title">{formattedNumber}</span> <span className="title" dir="auto">{formattedNumber}</span>
<span className="subtitle">{lang('Phone')}</span> <span className="subtitle">{lang('Phone')}</span>
</ListItem> </ListItem>
)} )}
@ -98,7 +98,7 @@ const ChatExtra: FC<OwnProps & StateProps & DispatchProps> = ({
ripple ripple
onClick={() => copy(`@${printedUsername}`, lang('Username'))} onClick={() => copy(`@${printedUsername}`, lang('Username'))}
> >
<span className="title">{renderText(printedUsername)}</span> <span className="title" dir="auto">{renderText(printedUsername)}</span>
<span className="subtitle">{lang('Username')}</span> <span className="subtitle">{lang('Username')}</span>
</ListItem> </ListItem>
)} )}
@ -109,7 +109,9 @@ const ChatExtra: FC<OwnProps & StateProps & DispatchProps> = ({
narrow narrow
isStatic isStatic
> >
<span className="title">{renderText(description, ['br', 'links', 'emoji'])}</span> <span className="title" dir="auto">
{renderText(description, ['br', 'links', 'emoji'])}
</span>
<span className="subtitle">{lang(userId ? 'UserBio' : 'Info')}</span> <span className="subtitle">{lang(userId ? 'UserBio' : 'Info')}</span>
</ListItem> </ListItem>
)} )}

View File

@ -80,7 +80,7 @@ const GifSearch: FC<StateProps & DispatchProps> = ({
if (!results.length) { if (!results.length) {
return ( return (
<p className="helper-text">{lang('NoGIFsFound')}</p> <p className="helper-text" dir="auto">{lang('NoGIFsFound')}</p>
); );
} }

View File

@ -116,7 +116,7 @@ const PollAnswerResults: FC<OwnProps & StateProps & DispatchProps> = ({
{voters && renderViewMoreButton()} {voters && renderViewMoreButton()}
</div> </div>
<div className="answer-head"> <div className="answer-head">
<span className="answer-title">{text}</span> <span className="answer-title" dir="auto">{text}</span>
<span className="answer-percent">{getPercentage(answerVote.votersCount, totalVoters)}%</span> <span className="answer-percent">{getPercentage(answerVote.votersCount, totalVoters)}%</span>
</div> </div>
</div> </div>

View File

@ -35,7 +35,7 @@ const PollResults: FC<StateProps> = ({
return ( return (
<div className="PollResults"> <div className="PollResults">
<h3 className="poll-question">{summary.question}</h3> <h3 className="poll-question" dir="auto">{summary.question}</h3>
<div className="poll-results-list custom-scroll"> <div className="poll-results-list custom-scroll">
{lastSyncTime && summary.answers.map((answer) => ( {lastSyncTime && summary.answers.map((answer) => (
<PollAnswerResults <PollAnswerResults

View File

@ -157,13 +157,13 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
if (user) { if (user) {
return ( return (
<div className={`status ${isUserOnline(user) ? 'online' : ''}`}> <div className={`status ${isUserOnline(user) ? 'online' : ''}`}>
<span className="user-status">{getUserStatus(lang, user)}</span> <span className="user-status" dir="auto">{getUserStatus(lang, user)}</span>
</div> </div>
); );
} }
return ( return (
<span className="status">{ <span className="status" dir="auto">{
isChatChannel(chat!) isChatChannel(chat!)
? lang('Subscribers', chat!.membersCount, 'i') ? lang('Subscribers', chat!.membersCount, 'i')
: lang('Members', chat!.membersCount, 'i') : lang('Members', chat!.membersCount, 'i')
@ -203,11 +203,11 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
<div className="info"> <div className="info">
{isSavedMessages ? ( {isSavedMessages ? (
<div className="title"> <div className="title">
<h3>{lang('SavedMessages')}</h3> <h3 dir="auto">{lang('SavedMessages')}</h3>
</div> </div>
) : ( ) : (
<div className="title"> <div className="title">
<h3>{fullName && renderText(fullName)}</h3> <h3 dir="auto">{fullName && renderText(fullName)}</h3>
{isVerifiedIconShown && <VerifiedIcon />} {isVerifiedIconShown && <VerifiedIcon />}
</div> </div>
)} )}

View File

@ -8,5 +8,7 @@
margin-bottom: 0.125rem; margin-bottom: 0.125rem;
font-weight: 500; font-weight: 500;
color: var(--color-text-secondary); color: var(--color-text-secondary);
unicode-bidi: plaintext;
text-align: initial;
} }
} }

View File

@ -108,10 +108,10 @@ const RightSearch: FC<OwnProps & StateProps & DispatchProps> = ({
<Avatar chat={senderChat} user={senderUser} /> <Avatar chat={senderChat} user={senderUser} />
<div className="info"> <div className="info">
<div className="title"> <div className="title">
<h3>{title && renderText(title)}</h3> <h3 dir="auto">{title && renderText(title)}</h3>
<LastMessageMeta message={message} /> <LastMessageMeta message={message} />
</div> </div>
<div className="subtitle"> <div className="subtitle" dir="auto">
{renderText(text, ['emoji', 'highlight'], { highlight: query })} {renderText(text, ['emoji', 'highlight'], { highlight: query })}
</div> </div>
</div> </div>
@ -127,13 +127,15 @@ const RightSearch: FC<OwnProps & StateProps & DispatchProps> = ({
onLoadMore={searchTextMessagesLocal} onLoadMore={searchTextMessagesLocal}
noFastList noFastList
> >
<p className="helper-text"> <p className="helper-text" dir="auto">
{!query ? ( {!query ? (
'Search messages' lang('lng_dlg_search_for_messages')
) : (totalCount === 0 || !foundResults.length) ? (
lang('lng_search_no_results')
) : totalCount === 1 ? ( ) : totalCount === 1 ? (
'1 message found' '1 message found'
) : ( ) : (
`${(foundResults.length && (totalCount || foundResults.length)) || 'No'} messages found` `${(foundResults.length && (totalCount || foundResults.length))} messages found`
)} )}
</p> </p>
{foundResults.map(renderSearchResult)} {foundResults.map(renderSearchResult)}

View File

@ -80,8 +80,8 @@ const StickerSetResult: FC<OwnProps & StateProps & DispatchProps> = ({
<div key={set.id} className="sticker-set"> <div key={set.id} className="sticker-set">
<div className="sticker-set-header"> <div className="sticker-set-header">
<div className="title-wrapper"> <div className="title-wrapper">
<h3 className="title">{set.title}</h3> <h3 className="title" dir="auto">{set.title}</h3>
<p className="count">{lang('Stickers', set.count, 'i')}</p> <p className="count" dir="auto">{lang('Stickers', set.count, 'i')}</p>
</div> </div>
<Button <Button
className={isAdded ? 'is-added' : undefined} className={isAdded ? 'is-added' : undefined}

View File

@ -200,8 +200,8 @@ const ManageChannel: FC<OwnProps & StateProps & DispatchProps> = ({
</div> </div>
<div className="section"> <div className="section">
<ListItem icon="group" multiline ripple onClick={handleClickSubscribers}> <ListItem icon="group" multiline ripple onClick={handleClickSubscribers}>
<span className="title">{lang('ChannelSubscribers')}</span> <span className="title" dir="auto">{lang('ChannelSubscribers')}</span>
<span className="subtitle">{lang('Subscribers', chat.membersCount!, 'i')}</span> <span className="subtitle" dir="auto">{lang('Subscribers', chat.membersCount!, 'i')}</span>
</ListItem> </ListItem>
</div> </div>
<div className="section"> <div className="section">

View File

@ -86,7 +86,7 @@ const ManageChatAdministrators: FC<OwnProps & StateProps> = ({
</div> </div>
<div className="section"> <div className="section">
<p className="text-muted"> <p className="text-muted" dir="auto">
{isChannel {isChannel
? 'You can add administrators to help you manage your channel.' ? 'You can add administrators to help you manage your channel.'
: 'You can add administrators to help you manage your group.'} : 'You can add administrators to help you manage your group.'}

View File

@ -135,7 +135,7 @@ const ManageChatPrivacyType: FC<OwnProps & StateProps & DispatchProps> = ({
checkUsername={checkPublicLink} checkUsername={checkPublicLink}
onChange={setUsername} onChange={setUsername}
/> />
<p className="section-info"> <p className="section-info" dir="auto">
{lang(`${langPrefix2}.Username.CreatePublicLinkHelp`)} {lang(`${langPrefix2}.Username.CreatePublicLinkHelp`)}
</p> </p>
</div> </div>

View File

@ -185,7 +185,7 @@ const ManageDiscussion: FC<OwnProps & StateProps & DispatchProps> = ({
function renderDiscussionGroups() { function renderDiscussionGroups() {
return ( return (
<div> <div>
<p className="section-help">{lang('DiscussionChannelHelp')}</p> <p className="section-help" dir="auto">{lang('DiscussionChannelHelp')}</p>
<div teactFastList> <div teactFastList>
<ListItem <ListItem
@ -212,7 +212,7 @@ const ManageDiscussion: FC<OwnProps & StateProps & DispatchProps> = ({
<NothingFound key="nothing-found" teactOrderKey={0} text="No discussion groups found" /> <NothingFound key="nothing-found" teactOrderKey={0} text="No discussion groups found" />
)} )}
</div> </div>
<p className="mt-4 mb-0 section-help">{lang('DiscussionChannelHelp2')}</p> <p className="mt-4 mb-0 section-help" dir="auto">{lang('DiscussionChannelHelp2')}</p>
<ConfirmDialog <ConfirmDialog
isOpen={isConfirmLinkGroupDialogOpen} isOpen={isConfirmLinkGroupDialogOpen}
onClose={closeConfirmLinkGroupDialog} onClose={closeConfirmLinkGroupDialog}

View File

@ -67,8 +67,8 @@ const Checkbox: FC<OwnProps> = ({
onChange={handleChange} onChange={handleChange}
/> />
<div className="Checkbox-main"> <div className="Checkbox-main">
<span className="label">{label}</span> <span className="label" dir="auto">{label}</span>
{subLabel && <span className="subLabel">{subLabel}</span>} {subLabel && <span className="subLabel" dir="auto">{subLabel}</span>}
</div> </div>
{isLoading && <Spinner />} {isLoading && <Spinner />}
</label> </label>

View File

@ -62,6 +62,7 @@ const InputText: FC<OwnProps> = ({
className="form-control" className="form-control"
type="text" type="text"
id={id} id={id}
dir="auto"
value={value || ''} value={value || ''}
placeholder={placeholder} placeholder={placeholder}
maxLength={maxLength} maxLength={maxLength}

View File

@ -20,6 +20,7 @@ const Link: FC<OwnProps> = ({ children, className, onClick }) => {
<a <a
href="#" href="#"
className={buildClassName('Link', className)} className={buildClassName('Link', className)}
dir="auto"
onClick={onClick ? handleClick : undefined} onClick={onClick ? handleClick : undefined}
> >
{children} {children}

View File

@ -13,6 +13,7 @@
color: var(--color-text); color: var(--color-text);
--ripple-color: rgba(0, 0, 0, .08); --ripple-color: rgba(0, 0, 0, .08);
cursor: pointer; cursor: pointer;
unicode-bidi: plaintext;
&:hover, &:focus { &:hover, &:focus {
background-color: var(--color-chat-hover); background-color: var(--color-chat-hover);

View File

@ -93,6 +93,7 @@ const SearchInput: FC<OwnProps> = ({
ref={inputRef} ref={inputRef}
id={inputId} id={inputId}
type="text" type="text"
dir="auto"
placeholder={placeholder || lang('Search')} placeholder={placeholder || lang('Search')}
className="form-control" className="form-control"
value={value} value={value}

View File

@ -22,6 +22,7 @@ type VirtualDomHead = {
}; };
const FILTERED_ATTRIBUTES = new Set(['key', 'ref', 'teactFastList', 'teactOrderKey']); const FILTERED_ATTRIBUTES = new Set(['key', 'ref', 'teactFastList', 'teactOrderKey']);
const HTML_ATTRIBUTES = new Set(['dir']);
const MAPPED_ATTRIBUTES: { [k: string]: string } = { const MAPPED_ATTRIBUTES: { [k: string]: string } = {
autoPlay: 'autoplay', autoPlay: 'autoplay',
autoComplete: 'autocomplete', autoComplete: 'autocomplete',
@ -428,7 +429,7 @@ function addAttribute(element: HTMLElement, key: string, value: any) {
element.style.cssText = value; element.style.cssText = value;
} else if (key.startsWith('on')) { } else if (key.startsWith('on')) {
addEventListener(element, key, value); addEventListener(element, key, value);
} else if (key.startsWith('data-')) { } else if (key.startsWith('data-') || HTML_ATTRIBUTES.has(key)) {
element.setAttribute(key, value); element.setAttribute(key, value);
} else if (!FILTERED_ATTRIBUTES.has(key)) { } else if (!FILTERED_ATTRIBUTES.has(key)) {
(element as any)[MAPPED_ATTRIBUTES[key] || key] = value; (element as any)[MAPPED_ATTRIBUTES[key] || key] = value;
@ -444,7 +445,7 @@ function removeAttribute(element: HTMLElement, key: string, value: any) {
element.style.cssText = ''; element.style.cssText = '';
} else if (key.startsWith('on')) { } else if (key.startsWith('on')) {
removeEventListener(element, key, value); removeEventListener(element, key, value);
} else if (key.startsWith('data-')) { } else if (key.startsWith('data-') || HTML_ATTRIBUTES.has(key)) {
element.removeAttribute(key); element.removeAttribute(key);
} else if (!FILTERED_ATTRIBUTES.has(key)) { } else if (!FILTERED_ATTRIBUTES.has(key)) {
delete (element as any)[MAPPED_ATTRIBUTES[key] || key]; delete (element as any)[MAPPED_ATTRIBUTES[key] || key];

View File

@ -120,6 +120,12 @@ addReducer('reset', () => {
cacheApi.clear(CUSTOM_BG_CACHE_NAME); cacheApi.clear(CUSTOM_BG_CACHE_NAME);
cacheApi.clear(LANG_CACHE_NAME); cacheApi.clear(LANG_CACHE_NAME);
const langChachePrefix = LANG_CACHE_NAME.replace(/\d+$/, '');
const langCacheVersion = (LANG_CACHE_NAME.match(/\d+$/) || [0])[0];
for (let i = 0; i < langCacheVersion; i++) {
cacheApi.clear(`${langChachePrefix}${i === 0 ? '' : i}`);
}
getDispatch().init(); getDispatch().init();
}); });