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;
min-width: 0;
flex-grow: 1;
text-align: initial;
}
.content-row {

View File

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

View File

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

View File

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

View File

@ -84,7 +84,7 @@ const GroupChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
function renderStatusOrTyping() {
if (withUpdatingStatus && !areMessagesLoaded && !isRestricted) {
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) {
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="title">
<h3>{renderText(getChatTitle(lang, chat))}</h3>
<h3 dir="auto">{renderText(getChatTitle(lang, chat))}</h3>
{chat.isVerified && <VerifiedIcon />}
</div>
{renderStatusOrTyping()}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -82,7 +82,7 @@ const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
</ListItem>
))
) : 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.'}
</p>
) : (

View File

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

View File

@ -95,15 +95,24 @@
.ListItem.search-result {
.ChatInfo {
.handle {
unicode-bidi: plaintext;
color: var(--color-primary);
&::before {
content: '@';
html[lang=ar] & {
content: ' ،@';
margin-inline-end: .25rem;
}
}
&::after {
content: ', ';
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">
<span className="title">{lang('BlockedUsers')}</span>
{blockedCount > 0 && (
<span className="subtitle">
<span className="subtitle" dir="auto">
{lang('Users', blockedCount)}
</span>
)}
@ -89,7 +89,9 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
>
<div className="multiline-menu-item">
<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>
</ListItem>
<ListItem
@ -100,7 +102,7 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
<div className="multiline-menu-item">
<span className="title">{lang('SessionsTitle')}</span>
{sessionsCount > 0 && (
<span className="subtitle">
<span className="subtitle" dir="auto">
{sessionsCount === 1 ? '1 session' : `${sessionsCount} sessions`}
</span>
)}
@ -117,7 +119,9 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
>
<div className="multiline-menu-item">
<span className="title">{lang('PrivacyPhoneTitle')}</span>
<span className="subtitle">{getVisibilityValue(visibilityPrivacyPhoneNumber)}</span>
<span className="subtitle" dir="auto">
{getVisibilityValue(visibilityPrivacyPhoneNumber)}
</span>
</div>
</ListItem>
<ListItem
@ -126,7 +130,9 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
>
<div className="multiline-menu-item">
<span className="title">{lang('LastSeenTitle')}</span>
<span className="subtitle">{getVisibilityValue(visibilityPrivacyLastSeen)}</span>
<span className="subtitle" dir="auto">
{getVisibilityValue(visibilityPrivacyLastSeen)}
</span>
</div>
</ListItem>
<ListItem
@ -135,7 +141,9 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
>
<div className="multiline-menu-item">
<span className="title">{lang('PrivacyProfilePhotoTitle')}</span>
<span className="subtitle">{getVisibilityValue(visibilityPrivacyProfilePhoto)}</span>
<span className="subtitle" dir="auto">
{getVisibilityValue(visibilityPrivacyProfilePhoto)}
</span>
</div>
</ListItem>
<ListItem
@ -144,7 +152,9 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
>
<div className="multiline-menu-item">
<span className="title">{lang('PrivacyForwardsTitle')}</span>
<span className="subtitle">{getVisibilityValue(visibilityPrivacyForwarding)}</span>
<span className="subtitle" dir="auto">
{getVisibilityValue(visibilityPrivacyForwarding)}
</span>
</div>
</ListItem>
<ListItem
@ -153,7 +163,9 @@ const SettingsPrivacy: FC<OwnProps & StateProps & DispatchProps> = ({
>
<div className="multiline-menu-item">
<span className="title">{lang('WhoCanAddMe')}</span>
<span className="subtitle">{getVisibilityValue(visibilityPrivacyGroupChats)}</span>
<span className="subtitle" dir="auto">
{getVisibilityValue(visibilityPrivacyGroupChats)}
</span>
</div>
</ListItem>
</div>

View File

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

View File

@ -121,7 +121,7 @@ const SettingsFoldersChatsPicker: FC<OwnProps> = ({
ripple
>
<i className={`icon-${type.icon}`} />
<h3 className="chat-type">{lang(type.title)}</h3>
<h3 className="chat-type" dir="auto">{lang(type.title)}</h3>
<Checkbox
label=""
checked={selectedChatTypes.includes(type.key)}
@ -179,7 +179,9 @@ const SettingsFoldersChatsPicker: FC<OwnProps> = ({
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>
<InfiniteScroll

View File

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

View File

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

View File

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

View File

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

View File

@ -31,6 +31,7 @@
padding: 0.5rem;
font-size: 1.25rem;
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}>
{text && (
<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>

View File

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

View File

@ -102,9 +102,9 @@ function renderAudio(audio: ApiAudio) {
return (
<>
<div className="title">{renderText(title || fileName)}</div>
<div className="title" dir="auto">{renderText(title || fileName)}</div>
{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) {
return (
<>
<div className="title">{senderName && renderText(senderName)}</div>
<div className="subtitle">{subtitle}</div>
<div className="title" dir="auto">{senderName && renderText(senderName)}</div>
<div className="subtitle" dir="auto">{subtitle}</div>
</>
);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -47,7 +47,9 @@ const EmojiCategory: FC<OwnProps> = ({
id={`emoji-category-${index}`}
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
className={buildClassName('symbol-set-container', transitionClassNames)}
// @ts-ignore

View File

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

View File

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

View File

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

View File

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

View File

@ -258,7 +258,7 @@ const TextFormatter: FC<OwnProps> = ({
}
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();
}, [
getSelectedElement, getSelectedText, onClose,
@ -282,7 +282,11 @@ const TextFormatter: FC<OwnProps> = ({
const text = getSelectedText();
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();
}
@ -424,6 +428,7 @@ const TextFormatter: FC<OwnProps> = ({
placeholder="Enter URL..."
autoComplete="off"
inputMode="url"
dir="auto"
onChange={handleLinkUrlChange}
onScroll={updateInputStyles}
/>

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@
display: flex;
align-items: center;
background: rgba(#999999, 0.6);
border-radius: .65rem;
border-radius: .625rem;
padding: 0 .25rem;
color: white;
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) & {
bottom: 0.9rem;
right: 0.8rem;
bottom: 0.935rem;
right: 0.8125rem;
}
.emoji-only & {
@ -80,6 +92,8 @@
.MessageOutgoingStatus {
margin-left: -.1875rem;
font-size: 1.1875rem;
border-radius: .625rem;
.Message.own & {
color: var(--color-accent-own);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -86,7 +86,7 @@ const ChatExtra: FC<OwnProps & StateProps & DispatchProps> = ({
<div className="ChatExtra">
{formattedNumber && !!formattedNumber.length && (
<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>
</ListItem>
)}
@ -98,7 +98,7 @@ const ChatExtra: FC<OwnProps & StateProps & DispatchProps> = ({
ripple
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>
</ListItem>
)}
@ -109,7 +109,9 @@ const ChatExtra: FC<OwnProps & StateProps & DispatchProps> = ({
narrow
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>
</ListItem>
)}

View File

@ -80,7 +80,7 @@ const GifSearch: FC<StateProps & DispatchProps> = ({
if (!results.length) {
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()}
</div>
<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>
</div>
</div>

View File

@ -35,7 +35,7 @@ const PollResults: FC<StateProps> = ({
return (
<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">
{lastSyncTime && summary.answers.map((answer) => (
<PollAnswerResults

View File

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

View File

@ -8,5 +8,7 @@
margin-bottom: 0.125rem;
font-weight: 500;
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} />
<div className="info">
<div className="title">
<h3>{title && renderText(title)}</h3>
<h3 dir="auto">{title && renderText(title)}</h3>
<LastMessageMeta message={message} />
</div>
<div className="subtitle">
<div className="subtitle" dir="auto">
{renderText(text, ['emoji', 'highlight'], { highlight: query })}
</div>
</div>
@ -127,13 +127,15 @@ const RightSearch: FC<OwnProps & StateProps & DispatchProps> = ({
onLoadMore={searchTextMessagesLocal}
noFastList
>
<p className="helper-text">
<p className="helper-text" dir="auto">
{!query ? (
'Search messages'
lang('lng_dlg_search_for_messages')
) : (totalCount === 0 || !foundResults.length) ? (
lang('lng_search_no_results')
) : totalCount === 1 ? (
'1 message found'
) : (
`${(foundResults.length && (totalCount || foundResults.length)) || 'No'} messages found`
`${(foundResults.length && (totalCount || foundResults.length))} messages found`
)}
</p>
{foundResults.map(renderSearchResult)}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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