Folders: Support custom emoji in title (#5385)
Co-authored-by: Dmitry Kabanov <dmitrykabanovdev@gmail.com> Co-authored-by: Dmitry Kabanov <153344039+dmitrykabanovdev@users.noreply.github.com>
This commit is contained in:
parent
e42b148721
commit
3b486614df
@ -26,7 +26,9 @@ import type {
|
||||
import { pick, pickTruthy } from '../../../util/iteratees';
|
||||
import { getServerTime, getServerTimeOffset } from '../../../util/serverTime';
|
||||
import { addPhotoToLocalDb, addUserToLocalDb, serializeBytes } from '../helpers';
|
||||
import { buildApiPhoto, buildApiUsernames, buildAvatarPhotoId } from './common';
|
||||
import {
|
||||
buildApiFormattedText, buildApiPhoto, buildApiUsernames, buildAvatarPhotoId,
|
||||
} from './common';
|
||||
import { omitVirtualClassFields } from './helpers';
|
||||
import {
|
||||
buildApiEmojiStatus,
|
||||
@ -419,7 +421,8 @@ export function buildApiChatFolder(filter: GramJs.DialogFilter | GramJs.DialogFi
|
||||
pinnedChatIds: filter.pinnedPeers.map(getApiChatIdFromMtpPeer).filter(Boolean),
|
||||
hasMyInvites: filter.hasMyInvites,
|
||||
isChatList: true,
|
||||
title: filter.title.text,
|
||||
noTitleAnimations: filter.titleNoanimate,
|
||||
title: buildApiFormattedText(filter.title),
|
||||
};
|
||||
}
|
||||
|
||||
@ -432,7 +435,8 @@ export function buildApiChatFolder(filter: GramJs.DialogFilter | GramJs.DialogFi
|
||||
pinnedChatIds: filter.pinnedPeers.map(getApiChatIdFromMtpPeer).filter(Boolean),
|
||||
includedChatIds: filter.includePeers.map(getApiChatIdFromMtpPeer).filter(Boolean),
|
||||
excludedChatIds: filter.excludePeers.map(getApiChatIdFromMtpPeer).filter(Boolean),
|
||||
title: filter.title.text,
|
||||
title: buildApiFormattedText(filter.title),
|
||||
noTitleAnimations: filter.titleNoanimate,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -238,6 +238,7 @@ export function buildFilterFromApiFolder(folder: ApiChatFolder): GramJs.DialogFi
|
||||
pinnedChatIds,
|
||||
includedChatIds,
|
||||
excludedChatIds,
|
||||
noTitleAnimations,
|
||||
} = folder;
|
||||
|
||||
const pinnedPeers = pinnedChatIds
|
||||
@ -255,17 +256,18 @@ export function buildFilterFromApiFolder(folder: ApiChatFolder): GramJs.DialogFi
|
||||
if (folder.isChatList) {
|
||||
return new GramJs.DialogFilterChatlist({
|
||||
id: folder.id,
|
||||
title: buildInputTextWithEntities({ text: folder.title, entities: [] }),
|
||||
title: buildInputTextWithEntities(folder.title),
|
||||
emoticon: emoticon || undefined,
|
||||
pinnedPeers,
|
||||
includePeers,
|
||||
hasMyInvites: folder.hasMyInvites,
|
||||
titleNoanimate: noTitleAnimations,
|
||||
});
|
||||
}
|
||||
|
||||
return new GramJs.DialogFilter({
|
||||
id: folder.id,
|
||||
title: buildInputTextWithEntities({ text: folder.title, entities: [] }),
|
||||
title: buildInputTextWithEntities(folder.title),
|
||||
emoticon: emoticon || undefined,
|
||||
contacts: contacts || undefined,
|
||||
nonContacts: nonContacts || undefined,
|
||||
@ -278,6 +280,7 @@ export function buildFilterFromApiFolder(folder: ApiChatFolder): GramJs.DialogFi
|
||||
pinnedPeers,
|
||||
includePeers,
|
||||
excludePeers,
|
||||
titleNoanimate: noTitleAnimations,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -207,7 +207,8 @@ export interface ApiRestrictionReason {
|
||||
|
||||
export interface ApiChatFolder {
|
||||
id: number;
|
||||
title: string;
|
||||
title: ApiFormattedText;
|
||||
noTitleAnimations?: true;
|
||||
description?: string;
|
||||
emoticon?: string;
|
||||
contacts?: true;
|
||||
|
||||
@ -1406,3 +1406,6 @@
|
||||
"StarsSubscribeBotText_one" = "Do you want to subscribe to **{name}** in **{bot}** for **{amount}** star per month?"
|
||||
"StarsSubscribeBotText_other" = "Do you want to subscribe to **{name}** in **{bot}** for **{amount}** stars per month?"
|
||||
"StarsSubscribeBotButtonMonth" = "Subscribe for {amount} / month";
|
||||
"FolderLinkTitleDescription" = "Anyone with this link can add {folder} folder and {chats} selected below.";
|
||||
"FolderLinkTitleDescriptionChats_one" = "the chat";
|
||||
"FolderLinkTitleDescriptionChats_other" = "the {count} chats";
|
||||
|
||||
@ -46,6 +46,7 @@ export function renderTextWithEntities({
|
||||
sharedCanvasHqRef,
|
||||
cacheBuster,
|
||||
forcePlayback,
|
||||
noCustomEmojiPlayback,
|
||||
focusedQuote,
|
||||
isInSelectMode,
|
||||
}: {
|
||||
@ -65,6 +66,7 @@ export function renderTextWithEntities({
|
||||
sharedCanvasHqRef?: React.RefObject<HTMLCanvasElement>;
|
||||
cacheBuster?: string;
|
||||
forcePlayback?: boolean;
|
||||
noCustomEmojiPlayback?: boolean;
|
||||
focusedQuote?: string;
|
||||
isInSelectMode?: boolean;
|
||||
}) {
|
||||
@ -173,6 +175,7 @@ export function renderTextWithEntities({
|
||||
sharedCanvasHqRef,
|
||||
cacheBuster,
|
||||
forcePlayback,
|
||||
noCustomEmojiPlayback,
|
||||
isInSelectMode,
|
||||
});
|
||||
|
||||
@ -388,6 +391,7 @@ function processEntity({
|
||||
sharedCanvasHqRef,
|
||||
cacheBuster,
|
||||
forcePlayback,
|
||||
noCustomEmojiPlayback,
|
||||
isInSelectMode,
|
||||
} : {
|
||||
entity: ApiMessageEntity;
|
||||
@ -407,6 +411,7 @@ function processEntity({
|
||||
sharedCanvasHqRef?: React.RefObject<HTMLCanvasElement>;
|
||||
cacheBuster?: string;
|
||||
forcePlayback?: boolean;
|
||||
noCustomEmojiPlayback?: boolean;
|
||||
isInSelectMode?: boolean;
|
||||
}) {
|
||||
const entityText = typeof entityContent === 'string' && entityContent;
|
||||
@ -447,6 +452,7 @@ function processEntity({
|
||||
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||
withTranslucentThumb={withTranslucentThumbs}
|
||||
forceAlways={forcePlayback}
|
||||
noPlay={noCustomEmojiPlayback}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@ -581,6 +587,7 @@ function processEntity({
|
||||
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||
withTranslucentThumb={withTranslucentThumbs}
|
||||
forceAlways={forcePlayback}
|
||||
noPlay={noCustomEmojiPlayback}
|
||||
/>
|
||||
);
|
||||
default:
|
||||
|
||||
@ -8,6 +8,7 @@ import type { ApiChatFolder } from '../../api/types';
|
||||
|
||||
import { ALL_FOLDER_ID } from '../../config';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { renderTextWithEntities } from '../common/helpers/renderTextWithEntities';
|
||||
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
|
||||
@ -59,10 +60,19 @@ const ChatFolderModal: FC<OwnProps & StateProps> = ({
|
||||
const [selectedFolderIds, setSelectedFolderIds] = useState<string[]>(initialSelectedFolderIds);
|
||||
|
||||
const folders = useMemo(() => {
|
||||
return folderOrderedIds?.filter((folderId) => folderId !== ALL_FOLDER_ID).map((folderId) => ({
|
||||
label: foldersById ? foldersById[folderId].title : '',
|
||||
value: String(folderId),
|
||||
})) || [];
|
||||
return folderOrderedIds?.filter((folderId) => folderId !== ALL_FOLDER_ID)
|
||||
.map((folderId) => {
|
||||
const folder = foldersById ? foldersById[folderId] : undefined;
|
||||
const label = folder ? renderTextWithEntities({
|
||||
text: folder.title.text,
|
||||
entities: folder.title.entities,
|
||||
noCustomEmojiPlayback: folder.noTitleAnimations,
|
||||
}) : '';
|
||||
return {
|
||||
label,
|
||||
value: String(folderId),
|
||||
};
|
||||
}) || [];
|
||||
}, [folderOrderedIds, foldersById]);
|
||||
|
||||
const handleSubmit = useCallback(() => {
|
||||
|
||||
@ -19,6 +19,7 @@ import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||
import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
||||
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
|
||||
|
||||
import useDerivedState from '../../../hooks/useDerivedState';
|
||||
import { useFolderManagerForUnreadCounters } from '../../../hooks/useFolderManager';
|
||||
@ -114,7 +115,7 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
const allChatsFolder: ApiChatFolder = useMemo(() => {
|
||||
return {
|
||||
id: ALL_FOLDER_ID,
|
||||
title: orderedFolderIds?.[0] === ALL_FOLDER_ID ? lang('FilterAllChatsShort') : lang('FilterAllChats'),
|
||||
title: { text: orderedFolderIds?.[0] === ALL_FOLDER_ID ? lang('FilterAllChatsShort') : lang('FilterAllChats') },
|
||||
includedChatIds: MEMO_EMPTY_ARRAY,
|
||||
excludedChatIds: MEMO_EMPTY_ARRAY,
|
||||
} satisfies ApiChatFolder;
|
||||
@ -197,7 +198,11 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
|
||||
return {
|
||||
id,
|
||||
title,
|
||||
title: renderTextWithEntities({
|
||||
text: title.text,
|
||||
entities: title.entities,
|
||||
noCustomEmojiPlayback: folder.noTitleAnimations,
|
||||
}),
|
||||
badgeCount: folderCountersById[id]?.chatsCount,
|
||||
isBadgeActive: Boolean(folderCountersById[id]?.notificationsCount),
|
||||
isBlocked,
|
||||
@ -335,7 +340,6 @@ const ChatFolders: FC<OwnProps & StateProps> = ({
|
||||
tabs={folderTabs}
|
||||
activeTab={activeChatFolder}
|
||||
onSwitchTab={handleSwitchTab}
|
||||
areFolders
|
||||
/>
|
||||
) : shouldRenderPlaceholder ? (
|
||||
<div ref={placeholderRef} className="tabs-placeholder" />
|
||||
|
||||
@ -298,7 +298,7 @@ const SettingsFoldersEdit: FC<OwnProps & StateProps> = ({
|
||||
<InputText
|
||||
className="mb-0"
|
||||
label={lang('FilterNameHint')}
|
||||
value={state.folder.title}
|
||||
value={state.folder.title.text}
|
||||
onChange={handleChange}
|
||||
error={state.error && state.error === ERROR_NO_TITLE ? ERROR_NO_TITLE : undefined}
|
||||
/>
|
||||
|
||||
@ -14,7 +14,7 @@ import { isBetween } from '../../../../util/math';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../../../util/memo';
|
||||
import { throttle } from '../../../../util/schedulers';
|
||||
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
||||
import renderText from '../../../common/helpers/renderText';
|
||||
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
|
||||
|
||||
import { useFolderManagerForChatsCount } from '../../../../hooks/useFolderManager';
|
||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||
@ -133,7 +133,10 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
||||
if (id === ALL_FOLDER_ID) {
|
||||
return {
|
||||
id,
|
||||
title: lang('FilterAllChats'),
|
||||
title: {
|
||||
text: lang('FilterAllChats'),
|
||||
entities: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -142,6 +145,7 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
||||
title: folder.title,
|
||||
subtitle: getFolderDescriptionText(lang, folder, chatsCountByFolderId[folder.id]),
|
||||
isChatList: folder.isChatList,
|
||||
noTitleAnimations: folder.noTitleAnimations,
|
||||
};
|
||||
});
|
||||
}, [folderIds, foldersById, lang, chatsCountByFolderId]);
|
||||
@ -252,7 +256,11 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
||||
allowSelection
|
||||
>
|
||||
<span className="title">
|
||||
{folder.title}
|
||||
{renderTextWithEntities({
|
||||
text: folder.title.text,
|
||||
entities: folder.title.entities,
|
||||
noCustomEmojiPlayback: folder.noTitleAnimations,
|
||||
})}
|
||||
</span>
|
||||
<span className="subtitle">{lang('FoldersAllChatsDesc')}</span>
|
||||
</ListItem>
|
||||
@ -297,7 +305,11 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
||||
}}
|
||||
>
|
||||
<span className="title">
|
||||
{renderText(folder.title, ['emoji'])}
|
||||
{renderTextWithEntities({
|
||||
text: folder.title.text,
|
||||
entities: folder.title.entities,
|
||||
noCustomEmojiPlayback: folder.noTitleAnimations,
|
||||
})}
|
||||
{isBlocked && <i className="icon icon-lock-badge settings-folders-blocked-icon" />}
|
||||
</span>
|
||||
<span className="subtitle">
|
||||
@ -330,7 +342,13 @@ const SettingsFoldersMain: FC<OwnProps & StateProps> = ({
|
||||
>
|
||||
<div className="settings-folders-recommended-item">
|
||||
<div className="multiline-item">
|
||||
<span className="title">{renderText(folder.title, ['emoji'])}</span>
|
||||
<span className="title">
|
||||
{renderTextWithEntities({
|
||||
text: folder.title.text,
|
||||
entities: folder.title.entities,
|
||||
noCustomEmojiPlayback: folder.noTitleAnimations,
|
||||
})}
|
||||
</span>
|
||||
<span className="subtitle">{folder.description}</span>
|
||||
</div>
|
||||
|
||||
|
||||
@ -4,6 +4,8 @@ import React, {
|
||||
} from '../../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../../global';
|
||||
|
||||
import type { ApiChatFolder } from '../../../../api/types';
|
||||
|
||||
import { STICKER_SIZE_FOLDER_SETTINGS } from '../../../../config';
|
||||
import { isChatChannel, isUserBot } from '../../../../global/helpers';
|
||||
import {
|
||||
@ -13,10 +15,11 @@ import {
|
||||
} from '../../../../global/selectors';
|
||||
import { partition } from '../../../../util/iteratees';
|
||||
import { LOCAL_TGS_URLS } from '../../../common/helpers/animatedAssets';
|
||||
import renderText from '../../../common/helpers/renderText';
|
||||
import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEntities';
|
||||
|
||||
import useEffectWithPrevDeps from '../../../../hooks/useEffectWithPrevDeps';
|
||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
||||
import useLang from '../../../../hooks/useLang';
|
||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../../hooks/useOldLang';
|
||||
|
||||
@ -33,9 +36,7 @@ type OwnProps = {
|
||||
|
||||
type StateProps = {
|
||||
folderId?: number;
|
||||
title?: string;
|
||||
includedChatIds?: string[];
|
||||
pinnedChatIds?: string[];
|
||||
folder?: ApiChatFolder;
|
||||
peerIds?: string[];
|
||||
url?: string;
|
||||
isLoading?: boolean;
|
||||
@ -45,9 +46,7 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
|
||||
isActive,
|
||||
onReset,
|
||||
folderId,
|
||||
title,
|
||||
includedChatIds,
|
||||
pinnedChatIds,
|
||||
folder,
|
||||
peerIds,
|
||||
url,
|
||||
isLoading,
|
||||
@ -55,7 +54,9 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
|
||||
const {
|
||||
createChatlistInvite, deleteChatlistInvite, editChatlistInvite, showNotification,
|
||||
} = getActions();
|
||||
const lang = useOldLang();
|
||||
|
||||
const lang = useLang();
|
||||
const oldLang = useOldLang();
|
||||
|
||||
const [isTouched, setIsTouched] = useState(false);
|
||||
|
||||
@ -84,8 +85,8 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
|
||||
const itemIds = useMemo(() => {
|
||||
return (includedChatIds || []).concat(pinnedChatIds || []);
|
||||
}, [includedChatIds, pinnedChatIds]);
|
||||
return (folder?.includedChatIds || []).concat(folder?.pinnedChatIds || []);
|
||||
}, [folder?.includedChatIds, folder?.pinnedChatIds]);
|
||||
|
||||
const [unlockedIds, lockedIds] = useMemo(() => {
|
||||
const global = getGlobal();
|
||||
@ -114,19 +115,19 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
|
||||
const chat = selectChat(global, id);
|
||||
if (user && isUserBot(user)) {
|
||||
showNotification({
|
||||
message: lang('FolderLinkScreen.AlertTextUnavailableBot'),
|
||||
message: oldLang('FolderLinkScreen.AlertTextUnavailableBot'),
|
||||
});
|
||||
} else if (user) {
|
||||
showNotification({
|
||||
message: lang('FolderLinkScreen.AlertTextUnavailableUser'),
|
||||
message: oldLang('FolderLinkScreen.AlertTextUnavailableUser'),
|
||||
});
|
||||
} else if (chat && isChatChannel(chat)) {
|
||||
showNotification({
|
||||
message: lang('FolderLinkScreen.AlertTextUnavailablePublicChannel'),
|
||||
message: oldLang('FolderLinkScreen.AlertTextUnavailablePublicChannel'),
|
||||
});
|
||||
} else {
|
||||
showNotification({
|
||||
message: lang('FolderLinkScreen.AlertTextUnavailablePublicGroup'),
|
||||
message: oldLang('FolderLinkScreen.AlertTextUnavailablePublicGroup'),
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -153,15 +154,26 @@ const SettingsShareChatlist: FC<OwnProps & StateProps> = ({
|
||||
className="settings-content-icon"
|
||||
/>
|
||||
|
||||
<p className="settings-item-description mb-3" dir="auto">
|
||||
{renderText(lang('FolderLinkScreen.TitleDescriptionSelected', [title, chatsCount]),
|
||||
['simple_markdown'])}
|
||||
</p>
|
||||
{folder && (
|
||||
<p className="settings-item-description mb-3" dir="auto">
|
||||
{lang('FolderLinkTitleDescription', {
|
||||
folder: renderTextWithEntities({
|
||||
text: folder.title.text,
|
||||
entities: folder.title.entities,
|
||||
noCustomEmojiPlayback: folder.noTitleAnimations,
|
||||
}),
|
||||
chats: lang('FolderLinkTitleDescriptionChats', { count: chatsCount }, { pluralValue: chatsCount }),
|
||||
}, {
|
||||
withMarkdown: true,
|
||||
withNodes: true,
|
||||
})}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<LinkField
|
||||
className="settings-item"
|
||||
link={!url ? lang('Loading') : url}
|
||||
link={!url ? oldLang('Loading') : url}
|
||||
withShare
|
||||
onRevoke={handleRevoke}
|
||||
isDisabled={!chatsCount || isTouched}
|
||||
@ -204,9 +216,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
return {
|
||||
folderId,
|
||||
title: folder?.title,
|
||||
includedChatIds: folder?.includedChatIds,
|
||||
pinnedChatIds: folder?.pinnedChatIds,
|
||||
folder,
|
||||
url,
|
||||
isLoading,
|
||||
peerIds: invite?.peerIds,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import type { FC, TeactNode } from '../../../lib/teact/teact';
|
||||
import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
@ -6,6 +6,7 @@ import type { ApiChatFolder } from '../../../api/types';
|
||||
import type { TabState } from '../../../global/types';
|
||||
|
||||
import { selectChatFolder } from '../../../global/selectors';
|
||||
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
|
||||
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated';
|
||||
@ -55,7 +56,13 @@ const ChatlistInviteModal: FC<OwnProps & StateProps> = ({
|
||||
}, [lang, renderingInfo]);
|
||||
|
||||
const renderingFolderTitle = useMemo(() => {
|
||||
if (renderingFolder) return renderingFolder.title;
|
||||
if (renderingFolder) {
|
||||
return renderTextWithEntities({
|
||||
text: renderingFolder.title.text,
|
||||
entities: renderingFolder.title.entities,
|
||||
noCustomEmojiPlayback: renderingFolder.noTitleAnimations,
|
||||
});
|
||||
}
|
||||
if (renderingInfo?.invite && 'title' in renderingInfo.invite) return renderingInfo.invite.title;
|
||||
return undefined;
|
||||
}, [renderingFolder, renderingInfo]);
|
||||
@ -66,12 +73,18 @@ const ChatlistInviteModal: FC<OwnProps & StateProps> = ({
|
||||
return undefined;
|
||||
}, [renderingInfo]);
|
||||
|
||||
function renderFolders(folderTitle: string) {
|
||||
function renderFolders(folderTitle: TeactNode) {
|
||||
return (
|
||||
<div className={styles.foldersWrapper}>
|
||||
<div className={styles.folders}>
|
||||
<Tab className={styles.folder} title={lang('FolderLinkPreviewLeft')} />
|
||||
<Tab className={styles.folder} isActive badgeCount={folderTabNumber} isBadgeActive title={folderTitle} />
|
||||
<Tab
|
||||
className={styles.folder}
|
||||
isActive
|
||||
badgeCount={folderTabNumber}
|
||||
isBadgeActive
|
||||
title={folderTitle}
|
||||
/>
|
||||
<Tab className={styles.folder} title={lang('FolderLinkPreviewRight')} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -49,6 +49,7 @@
|
||||
display: flex;
|
||||
align-items: center;
|
||||
white-space: nowrap;
|
||||
gap: 1px; // Prevent custom emoji sticking to the text
|
||||
}
|
||||
|
||||
.badge {
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import type { FC, TeactNode } from '../../lib/teact/teact';
|
||||
import React, { useEffect, useLayoutEffect, useRef } from '../../lib/teact/teact';
|
||||
|
||||
import type { MenuItemContextAction } from './ListItem';
|
||||
@ -21,7 +21,7 @@ import './Tab.scss';
|
||||
|
||||
type OwnProps = {
|
||||
className?: string;
|
||||
title: string;
|
||||
title: TeactNode;
|
||||
isActive?: boolean;
|
||||
isBlocked?: boolean;
|
||||
badgeCount?: number;
|
||||
@ -139,7 +139,7 @@ const Tab: FC<OwnProps> = ({
|
||||
ref={tabRef}
|
||||
>
|
||||
<span className="Tab_inner">
|
||||
{renderText(title)}
|
||||
{typeof title === 'string' ? renderText(title) : title}
|
||||
{Boolean(badgeCount) && (
|
||||
<span className={buildClassName('badge', isBadgeActive && classNames.badgeActive)}>{badgeCount}</span>
|
||||
)}
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import type { FC, TeactNode } from '../../lib/teact/teact';
|
||||
import React, { memo, useEffect, useRef } from '../../lib/teact/teact';
|
||||
|
||||
import type { MenuItemContextAction } from './ListItem';
|
||||
|
||||
import { ALL_FOLDER_ID } from '../../config';
|
||||
import animateHorizontalScroll from '../../util/animateHorizontalScroll';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { IS_ANDROID, IS_IOS } from '../../util/windowEnvironment';
|
||||
@ -18,7 +17,7 @@ import './TabList.scss';
|
||||
|
||||
export type TabWithProperties = {
|
||||
id?: number;
|
||||
title: string;
|
||||
title: TeactNode;
|
||||
badgeCount?: number;
|
||||
isBlocked?: boolean;
|
||||
isBadgeActive?: boolean;
|
||||
@ -27,7 +26,6 @@ export type TabWithProperties = {
|
||||
|
||||
type OwnProps = {
|
||||
tabs: readonly TabWithProperties[];
|
||||
areFolders?: boolean;
|
||||
activeTab: number;
|
||||
className?: string;
|
||||
tabClassName?: string;
|
||||
@ -40,7 +38,7 @@ const TAB_SCROLL_THRESHOLD_PX = 16;
|
||||
const SCROLL_DURATION = IS_IOS ? 450 : IS_ANDROID ? 400 : 300;
|
||||
|
||||
const TabList: FC<OwnProps> = ({
|
||||
tabs, areFolders, activeTab, onSwitchTab,
|
||||
tabs, activeTab, onSwitchTab,
|
||||
contextRootElementSelector, className, tabClassName,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
@ -83,9 +81,8 @@ const TabList: FC<OwnProps> = ({
|
||||
>
|
||||
{tabs.map((tab, i) => (
|
||||
<Tab
|
||||
key={tab.id ?? tab.title}
|
||||
// TODO Remove dependency on usage context
|
||||
title={(!areFolders || tab.id === ALL_FOLDER_ID) ? lang(tab.title) : tab.title}
|
||||
key={tab.id}
|
||||
title={tab.title}
|
||||
isActive={i === activeTab}
|
||||
isBlocked={tab.isBlocked}
|
||||
badgeCount={tab.badgeCount}
|
||||
|
||||
@ -124,7 +124,7 @@ const INITIAL_STATE: FoldersState = {
|
||||
mode: 'create',
|
||||
chatFilter: '',
|
||||
folder: {
|
||||
title: '',
|
||||
title: { text: '' },
|
||||
includedChatIds: [],
|
||||
excludedChatIds: [],
|
||||
},
|
||||
@ -133,14 +133,14 @@ const INITIAL_STATE: FoldersState = {
|
||||
const foldersReducer: StateReducer<FoldersState, FoldersActions> = (
|
||||
state,
|
||||
action,
|
||||
) => {
|
||||
): FoldersState => {
|
||||
switch (action.type) {
|
||||
case 'setTitle':
|
||||
return {
|
||||
...state,
|
||||
folder: {
|
||||
...state.folder,
|
||||
title: action.payload,
|
||||
title: { text: action.payload },
|
||||
},
|
||||
isTouched: true,
|
||||
};
|
||||
@ -184,7 +184,7 @@ const foldersReducer: StateReducer<FoldersState, FoldersActions> = (
|
||||
...state,
|
||||
folder: {
|
||||
...omit(state.folder, INCLUDE_FILTER_FIELDS),
|
||||
title: state.folder.title ? state.folder.title : getSuggestedFolderName(state.includeFilters),
|
||||
title: state.folder.title ? state.folder.title : { text: getSuggestedFolderName(state.includeFilters) },
|
||||
...state.includeFilters,
|
||||
},
|
||||
includeFilters: undefined,
|
||||
|
||||
7
src/types/language.d.ts
vendored
7
src/types/language.d.ts
vendored
@ -1574,6 +1574,10 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
||||
'StarsSubscribeBotButtonMonth': {
|
||||
'amount': V;
|
||||
};
|
||||
'FolderLinkTitleDescription': {
|
||||
'folder': V;
|
||||
'chats': V;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LangPairPlural {
|
||||
@ -1755,6 +1759,9 @@ export interface LangPairPluralWithVariables<V extends unknown = LangVariable> {
|
||||
'bot': V;
|
||||
'amount': V;
|
||||
};
|
||||
'FolderLinkTitleDescriptionChats': {
|
||||
'count': V;
|
||||
};
|
||||
}
|
||||
export type RegularLangKey = keyof LangPair;
|
||||
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;
|
||||
|
||||
@ -122,11 +122,11 @@ export type LangFn = {
|
||||
<K extends PluralLangKey = PluralLangKey>(
|
||||
key: K, variables: undefined, options: LangFnOptionsWithPlural,
|
||||
): string;
|
||||
<K extends RegularLangKeyWithVariables = RegularLangKeyWithVariables, V = LangPairWithVariables[K]>(
|
||||
key: K, variables: V, options?: LangFnOptions,
|
||||
<K extends RegularLangKeyWithVariables = RegularLangKeyWithVariables>(
|
||||
key: K, variables: LangPairWithVariables[K], options?: LangFnOptions,
|
||||
): string;
|
||||
<K extends PluralLangKeyWithVariables = PluralLangKeyWithVariables, V = LangPairPluralWithVariables[K]>(
|
||||
key: K, variables: V, options: LangFnOptionsWithPlural,
|
||||
<K extends PluralLangKeyWithVariables = PluralLangKeyWithVariables>(
|
||||
key: K, variables: LangPairPluralWithVariables[K], options: LangFnOptionsWithPlural,
|
||||
): string;
|
||||
|
||||
<K extends RegularLangKey = RegularLangKey>(
|
||||
@ -135,11 +135,11 @@ export type LangFn = {
|
||||
<K extends PluralLangKey = PluralLangKey>(
|
||||
key: K, variables: undefined, options: AdvancedLangFnOptionsWithPlural,
|
||||
): TeactNode;
|
||||
<K extends RegularLangKeyWithVariables = RegularLangKeyWithVariables, V = LangPairWithVariables[K]>(
|
||||
key: K, variables: V, options: AdvancedLangFnOptions,
|
||||
<K extends RegularLangKeyWithVariables = RegularLangKeyWithVariables>(
|
||||
key: K, variables: LangPairWithVariables<TeactNode | undefined>[K], options: AdvancedLangFnOptions,
|
||||
): TeactNode;
|
||||
<K extends PluralLangKeyWithVariables = PluralLangKeyWithVariables, V = LangPairPluralWithVariables[K]>(
|
||||
key: K, variables: V, options: AdvancedLangFnOptionsWithPlural,
|
||||
<K extends PluralLangKeyWithVariables = PluralLangKeyWithVariables>(
|
||||
key: K, variables: LangPairPluralWithVariables<TeactNode | undefined>[K], options: AdvancedLangFnOptionsWithPlural,
|
||||
): TeactNode;
|
||||
|
||||
with: (params: LangFnParameters) => TeactNode;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user