Chat Picker: Support web bot and share deep links, refactoring (#2037)
This commit is contained in:
parent
d896bb507d
commit
45a42f0d6c
@ -1,7 +1,7 @@
|
||||
import { Api as GramJs } from '../../../lib/gramjs';
|
||||
import type {
|
||||
ApiAttachMenuBot,
|
||||
ApiAttachMenuBotIcon,
|
||||
ApiAttachBot,
|
||||
ApiAttachBotIcon,
|
||||
ApiAttachMenuPeerType,
|
||||
ApiBotCommand,
|
||||
ApiBotInfo,
|
||||
@ -62,7 +62,7 @@ export function buildBotSwitchPm(switchPm?: GramJs.InlineBotSwitchPM) {
|
||||
return switchPm ? pick(switchPm, ['text', 'startParam']) as ApiBotInlineSwitchPm : undefined;
|
||||
}
|
||||
|
||||
export function buildApiAttachMenuBot(bot: GramJs.AttachMenuBot): ApiAttachMenuBot {
|
||||
export function buildApiAttachBot(bot: GramJs.AttachMenuBot): ApiAttachBot {
|
||||
return {
|
||||
id: bot.botId.toString(),
|
||||
hasSettings: bot.hasSettings,
|
||||
@ -73,15 +73,15 @@ export function buildApiAttachMenuBot(bot: GramJs.AttachMenuBot): ApiAttachMenuB
|
||||
}
|
||||
|
||||
function buildApiAttachMenuPeerType(peerType: GramJs.TypeAttachMenuPeerType): ApiAttachMenuPeerType {
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeBotPM) return 'bot';
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypePM) return 'private';
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeChat) return 'chat';
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeBroadcast) return 'channel';
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeBotPM) return 'bots';
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypePM) return 'users';
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeChat) return 'chats';
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeBroadcast) return 'channels';
|
||||
if (peerType instanceof GramJs.AttachMenuPeerTypeSameBotPM) return 'self';
|
||||
return undefined!; // Never reached
|
||||
}
|
||||
|
||||
function buildApiAttachMenuIcon(icon: GramJs.AttachMenuBotIcon): ApiAttachMenuBotIcon | undefined {
|
||||
function buildApiAttachMenuIcon(icon: GramJs.AttachMenuBotIcon): ApiAttachBotIcon | undefined {
|
||||
if (!(icon.icon instanceof GramJs.Document)) return undefined;
|
||||
|
||||
const document = buildApiDocument(icon.icon);
|
||||
|
||||
@ -68,7 +68,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
||||
...(avatarHash && { avatarHash }),
|
||||
hasVideoAvatar,
|
||||
...(mtpUser.bot && mtpUser.botInlinePlaceholder && { botPlaceholder: mtpUser.botInlinePlaceholder }),
|
||||
...(mtpUser.bot && mtpUser.botAttachMenu && { isAttachMenuBot: mtpUser.botAttachMenu }),
|
||||
...(mtpUser.bot && mtpUser.botAttachMenu && { isAttachBot: mtpUser.botAttachMenu }),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -10,7 +10,7 @@ import { invokeRequest } from './client';
|
||||
import { buildInputPeer, buildInputThemeParams, generateRandomBigInt } from '../gramjsBuilders';
|
||||
import { buildApiUser } from '../apiBuilders/users';
|
||||
import {
|
||||
buildApiAttachMenuBot, buildApiBotInlineMediaResult, buildApiBotInlineResult, buildBotSwitchPm,
|
||||
buildApiAttachBot, buildApiBotInlineMediaResult, buildApiBotInlineResult, buildBotSwitchPm,
|
||||
} from '../apiBuilders/bots';
|
||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||
import { addEntitiesWithPhotosToLocalDb, addUserToLocalDb, deserializeBytes } from '../helpers';
|
||||
@ -249,7 +249,7 @@ export async function sendWebViewData({
|
||||
}), true);
|
||||
}
|
||||
|
||||
export async function loadAttachMenuBots({
|
||||
export async function loadAttachBots({
|
||||
hash,
|
||||
}: {
|
||||
hash?: string;
|
||||
@ -262,13 +262,13 @@ export async function loadAttachMenuBots({
|
||||
addEntitiesWithPhotosToLocalDb(result.users);
|
||||
return {
|
||||
hash: result.hash.toString(),
|
||||
bots: buildCollectionByKey(result.bots.map(buildApiAttachMenuBot), 'id'),
|
||||
bots: buildCollectionByKey(result.bots.map(buildApiAttachBot), 'id'),
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function toggleBotInAttachMenu({
|
||||
export function toggleAttachBot({
|
||||
bot,
|
||||
isEnabled,
|
||||
}: {
|
||||
|
||||
@ -68,7 +68,7 @@ export {
|
||||
|
||||
export {
|
||||
answerCallbackButton, fetchTopInlineBots, fetchInlineBot, fetchInlineBotResults, sendInlineBotResult, startBot,
|
||||
requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachMenuBots, toggleBotInAttachMenu,
|
||||
requestWebView, requestSimpleWebView, sendWebViewData, prolongWebView, loadAttachBots, toggleAttachBot,
|
||||
requestBotUrlAuth, requestLinkUrlAuth, acceptBotUrlAuth, acceptLinkUrlAuth,
|
||||
} from './bots';
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { ApiDocument, ApiPhoto } from './messages';
|
||||
import type { ApiBotInfo } from './bots';
|
||||
import type { API_CHAT_TYPES } from '../../config';
|
||||
|
||||
export interface ApiUser {
|
||||
id: string;
|
||||
@ -26,7 +27,7 @@ export interface ApiUser {
|
||||
isFullyLoaded: boolean;
|
||||
};
|
||||
fakeType?: ApiFakeType;
|
||||
isAttachMenuBot?: boolean;
|
||||
isAttachBot?: boolean;
|
||||
|
||||
// Obtained from GetFullUser / UserFullInfo
|
||||
fullInfo?: ApiUserFullInfo;
|
||||
@ -56,17 +57,18 @@ export interface ApiUserStatus {
|
||||
expires?: number;
|
||||
}
|
||||
|
||||
export type ApiAttachMenuPeerType = 'self' | 'bot' | 'private' | 'chat' | 'channel';
|
||||
export type ApiChatType = typeof API_CHAT_TYPES[number];
|
||||
export type ApiAttachMenuPeerType = 'self' | ApiChatType;
|
||||
|
||||
export interface ApiAttachMenuBot {
|
||||
export interface ApiAttachBot {
|
||||
id: string;
|
||||
hasSettings?: boolean;
|
||||
shortName: string;
|
||||
peerTypes: ApiAttachMenuPeerType[];
|
||||
icons: ApiAttachMenuBotIcon[];
|
||||
icons: ApiAttachBotIcon[];
|
||||
}
|
||||
|
||||
export interface ApiAttachMenuBotIcon {
|
||||
export interface ApiAttachBotIcon {
|
||||
name: string;
|
||||
document: ApiDocument;
|
||||
}
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
// eslint-disable-next-line import/no-cycle
|
||||
export { default as MediaViewer } from '../components/mediaViewer/MediaViewer';
|
||||
|
||||
export { default as ForwardPicker } from '../components/main/ForwardPicker';
|
||||
export { default as ForwardRecipientPicker } from '../components/main/ForwardRecipientPicker';
|
||||
export { default as DraftRecipientPicker } from '../components/main/DraftRecipientPicker';
|
||||
export { default as AttachBotRecipientPicker } from '../components/main/AttachBotRecipientPicker';
|
||||
export { default as Dialogs } from '../components/main/Dialogs';
|
||||
export { default as Notifications } from '../components/main/Notifications';
|
||||
export { default as SafeLinkModal } from '../components/main/SafeLinkModal';
|
||||
@ -10,7 +12,7 @@ export { default as HistoryCalendar } from '../components/main/HistoryCalendar';
|
||||
export { default as NewContactModal } from '../components/main/NewContactModal';
|
||||
export { default as WebAppModal } from '../components/main/WebAppModal';
|
||||
export { default as BotTrustModal } from '../components/main/BotTrustModal';
|
||||
export { default as BotAttachModal } from '../components/main/BotAttachModal';
|
||||
export { default as AttachBotInstallModal } from '../components/main/AttachBotInstallModal';
|
||||
export { default as DeleteFolderDialog } from '../components/main/DeleteFolderDialog';
|
||||
export { default as PremiumMainModal } from '../components/main/premium/PremiumMainModal';
|
||||
export { default as GiftPremiumModal } from '../components/main/premium/GiftPremiumModal';
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
import type { RefObject } from 'react';
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo, useRef, useCallback } from '../../lib/teact/teact';
|
||||
|
||||
@ -24,11 +23,10 @@ export type OwnProps = {
|
||||
currentUserId?: string;
|
||||
chatOrUserIds: string[];
|
||||
isOpen: boolean;
|
||||
filterRef: RefObject<HTMLInputElement>;
|
||||
filterPlaceholder: string;
|
||||
filter: string;
|
||||
searchPlaceholder: string;
|
||||
search: string;
|
||||
loadMore?: NoneToVoidFunction;
|
||||
onFilterChange: (filter: string) => void;
|
||||
onSearchChange: (search: string) => void;
|
||||
onSelectChatOrUser: (chatOrUserId: string) => void;
|
||||
onClose: NoneToVoidFunction;
|
||||
onCloseAnimationEnd?: NoneToVoidFunction;
|
||||
@ -38,28 +36,29 @@ const ChatOrUserPicker: FC<OwnProps> = ({
|
||||
isOpen,
|
||||
currentUserId,
|
||||
chatOrUserIds,
|
||||
filterRef,
|
||||
filter,
|
||||
filterPlaceholder,
|
||||
search,
|
||||
searchPlaceholder,
|
||||
loadMore,
|
||||
onFilterChange,
|
||||
onSearchChange,
|
||||
onSelectChatOrUser,
|
||||
onClose,
|
||||
onCloseAnimationEnd,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
const [viewportIds, getMore] = useInfiniteScroll(loadMore, chatOrUserIds, Boolean(filter));
|
||||
const [viewportIds, getMore] = useInfiniteScroll(loadMore, chatOrUserIds, Boolean(search));
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const searchRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const resetFilter = useCallback(() => {
|
||||
onFilterChange('');
|
||||
}, [onFilterChange]);
|
||||
useInputFocusOnOpen(filterRef, isOpen, resetFilter);
|
||||
const resetSearch = useCallback(() => {
|
||||
onSearchChange('');
|
||||
}, [onSearchChange]);
|
||||
useInputFocusOnOpen(searchRef, isOpen, resetSearch);
|
||||
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
const handleFilterChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onFilterChange(e.currentTarget.value);
|
||||
}, [onFilterChange]);
|
||||
const handleSearchChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
onSearchChange(e.currentTarget.value);
|
||||
}, [onSearchChange]);
|
||||
const handleKeyDown = useKeyboardListNavigation(containerRef, isOpen, (index) => {
|
||||
if (viewportIds && viewportIds.length > 0) {
|
||||
onSelectChatOrUser(viewportIds[index === -1 ? 0 : index]);
|
||||
@ -78,11 +77,11 @@ const ChatOrUserPicker: FC<OwnProps> = ({
|
||||
<i className="icon-close" />
|
||||
</Button>
|
||||
<InputText
|
||||
ref={filterRef}
|
||||
value={filter}
|
||||
onChange={handleFilterChange}
|
||||
ref={searchRef}
|
||||
value={search}
|
||||
onChange={handleSearchChange}
|
||||
onKeyDown={handleKeyDown}
|
||||
placeholder={filterPlaceholder}
|
||||
placeholder={searchPlaceholder}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
128
src/components/common/RecipientPicker.tsx
Normal file
128
src/components/common/RecipientPicker.tsx
Normal file
@ -0,0 +1,128 @@
|
||||
import React, { memo, useMemo, useState } from '../../lib/teact/teact';
|
||||
import { getGlobal, withGlobal } from '../../global';
|
||||
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import type { ApiChat, ApiChatType } from '../../api/types';
|
||||
import { MAIN_THREAD_ID } from '../../api/types';
|
||||
|
||||
import { API_CHAT_TYPES } from '../../config';
|
||||
import { unique } from '../../util/iteratees';
|
||||
import {
|
||||
filterChatsByName,
|
||||
filterUsersByName,
|
||||
getCanPostInChat,
|
||||
isDeletedUser,
|
||||
sortChatIds,
|
||||
} from '../../global/helpers';
|
||||
|
||||
import useLang from '../../hooks/useLang';
|
||||
|
||||
import ChatOrUserPicker from './ChatOrUserPicker';
|
||||
import { filterChatIdsByType } from '../../global/selectors';
|
||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen: boolean;
|
||||
searchPlaceholder: string;
|
||||
filter?: ApiChatType[];
|
||||
loadMore?: NoneToVoidFunction;
|
||||
onSelectRecipient: (peerId: string) => void;
|
||||
onClose: NoneToVoidFunction;
|
||||
onCloseAnimationEnd?: NoneToVoidFunction;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
currentUserId?: string;
|
||||
chatsById: Record<string, ApiChat>;
|
||||
activeListIds?: string[];
|
||||
archivedListIds?: string[];
|
||||
pinnedIds?: string[];
|
||||
contactIds?: string[];
|
||||
};
|
||||
|
||||
const RecipientPicker: FC<OwnProps & StateProps> = ({
|
||||
isOpen,
|
||||
currentUserId,
|
||||
chatsById,
|
||||
activeListIds,
|
||||
archivedListIds,
|
||||
pinnedIds,
|
||||
contactIds,
|
||||
filter = API_CHAT_TYPES,
|
||||
searchPlaceholder,
|
||||
loadMore,
|
||||
onSelectRecipient,
|
||||
onClose,
|
||||
onCloseAnimationEnd,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
const [search, setSearch] = useState('');
|
||||
const ids = useMemo(() => {
|
||||
if (!isOpen) return undefined;
|
||||
|
||||
let priorityIds = pinnedIds || [];
|
||||
if (currentUserId) {
|
||||
priorityIds = unique([currentUserId, ...priorityIds]);
|
||||
}
|
||||
|
||||
// No need for expensive global updates on users, so we avoid them
|
||||
const global = getGlobal();
|
||||
const usersById = global.users.byId;
|
||||
|
||||
const chatIds = [
|
||||
...(activeListIds || []),
|
||||
...((search && archivedListIds) || []),
|
||||
].filter((id) => {
|
||||
const chat = chatsById[id];
|
||||
const user = usersById[id];
|
||||
if (user && isDeletedUser(user)) return false;
|
||||
|
||||
return chat && getCanPostInChat(chat, MAIN_THREAD_ID);
|
||||
});
|
||||
|
||||
const sorted = sortChatIds(unique([
|
||||
...filterChatsByName(lang, chatIds, chatsById, search, currentUserId),
|
||||
...(contactIds && filter.includes('users') ? filterUsersByName(contactIds, usersById, search) : []),
|
||||
]), chatsById, undefined, priorityIds);
|
||||
|
||||
return filterChatIdsByType(global, sorted, filter);
|
||||
}, [pinnedIds, currentUserId, activeListIds, search, archivedListIds, lang, chatsById, contactIds, filter, isOpen]);
|
||||
|
||||
const renderingIds = useCurrentOrPrev(ids, true)!;
|
||||
|
||||
return (
|
||||
<ChatOrUserPicker
|
||||
isOpen={isOpen}
|
||||
chatOrUserIds={renderingIds}
|
||||
searchPlaceholder={searchPlaceholder}
|
||||
search={search}
|
||||
onSearchChange={setSearch}
|
||||
loadMore={loadMore}
|
||||
onSelectChatOrUser={onSelectRecipient}
|
||||
onClose={onClose}
|
||||
onCloseAnimationEnd={onCloseAnimationEnd}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const {
|
||||
chats: {
|
||||
byId: chatsById,
|
||||
listIds,
|
||||
orderedPinnedIds,
|
||||
},
|
||||
currentUserId,
|
||||
} = global;
|
||||
|
||||
return {
|
||||
chatsById,
|
||||
activeListIds: listIds.active,
|
||||
archivedListIds: listIds.archived,
|
||||
pinnedIds: orderedPinnedIds.active,
|
||||
contactIds: global.contactList?.userIds,
|
||||
currentUserId,
|
||||
};
|
||||
},
|
||||
)(RecipientPicker));
|
||||
@ -1,6 +1,6 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
useMemo, useState, memo, useRef, useCallback, useEffect,
|
||||
useMemo, useState, memo, useCallback, useEffect,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
@ -40,13 +40,11 @@ const BlockUserModal: FC<OwnProps & StateProps> = ({
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
const [filter, setFilter] = useState('');
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const filterRef = useRef<HTMLInputElement>(null);
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
setUserSearchQuery({ query: filter });
|
||||
}, [filter, setUserSearchQuery]);
|
||||
setUserSearchQuery({ query: search });
|
||||
}, [search, setUserSearchQuery]);
|
||||
|
||||
const filteredContactIds = useMemo(() => {
|
||||
const availableContactIds = unique([
|
||||
@ -56,14 +54,14 @@ const BlockUserModal: FC<OwnProps & StateProps> = ({
|
||||
return contactId !== currentUserId && !blockedIds.includes(contactId);
|
||||
}));
|
||||
|
||||
return filterUsersByName(availableContactIds, usersById, filter)
|
||||
return filterUsersByName(availableContactIds, usersById, search)
|
||||
.sort((firstId, secondId) => {
|
||||
const firstName = getUserFullName(usersById[firstId]) || '';
|
||||
const secondName = getUserFullName(usersById[secondId]) || '';
|
||||
|
||||
return firstName.localeCompare(secondName);
|
||||
});
|
||||
}, [blockedIds, contactIds, currentUserId, filter, localContactIds, usersById]);
|
||||
}, [blockedIds, contactIds, currentUserId, search, localContactIds, usersById]);
|
||||
|
||||
const handleRemoveUser = useCallback((userId: string) => {
|
||||
const { id: contactId, accessHash } = usersById[userId] || {};
|
||||
@ -78,10 +76,9 @@ const BlockUserModal: FC<OwnProps & StateProps> = ({
|
||||
<ChatOrUserPicker
|
||||
isOpen={isOpen}
|
||||
chatOrUserIds={filteredContactIds}
|
||||
filterRef={filterRef}
|
||||
filterPlaceholder={lang('BlockedUsers.BlockUser')}
|
||||
filter={filter}
|
||||
onFilterChange={setFilter}
|
||||
searchPlaceholder={lang('BlockedUsers.BlockUser')}
|
||||
search={search}
|
||||
onSearchChange={setSearch}
|
||||
onSelectChatOrUser={handleRemoveUser}
|
||||
onClose={onClose}
|
||||
/>
|
||||
|
||||
17
src/components/main/AttachBotInstallModal.async.tsx
Normal file
17
src/components/main/AttachBotInstallModal.async.tsx
Normal file
@ -0,0 +1,17 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import { Bundles } from '../../util/moduleLoader';
|
||||
|
||||
import type { OwnProps } from './AttachBotInstallModal';
|
||||
|
||||
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||
|
||||
const AttachBotInstallModalAsync: FC<OwnProps> = (props) => {
|
||||
const { bot } = props;
|
||||
const AttachBotInstallModal = useModuleLoader(Bundles.Extra, 'AttachBotInstallModal', !bot);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return AttachBotInstallModal ? <AttachBotInstallModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(AttachBotInstallModalAsync);
|
||||
@ -1,7 +1,7 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React from '../../lib/teact/teact';
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import { getActions } from '../../global';
|
||||
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import type { ApiUser } from '../../api/types';
|
||||
|
||||
import useLang from '../../hooks/useLang';
|
||||
@ -12,10 +12,10 @@ export type OwnProps = {
|
||||
bot?: ApiUser;
|
||||
};
|
||||
|
||||
const BotAttachModal: FC<OwnProps> = ({
|
||||
const AttachBotInstallModal: FC<OwnProps> = ({
|
||||
bot,
|
||||
}) => {
|
||||
const { closeBotAttachRequestModal, confirmBotAttachRequest } = getActions();
|
||||
const { cancelAttachBotInstall, confirmAttachBotInstall } = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
@ -24,12 +24,12 @@ const BotAttachModal: FC<OwnProps> = ({
|
||||
return (
|
||||
<ConfirmDialog
|
||||
isOpen={Boolean(bot)}
|
||||
onClose={closeBotAttachRequestModal}
|
||||
confirmHandler={confirmBotAttachRequest}
|
||||
onClose={cancelAttachBotInstall}
|
||||
confirmHandler={confirmAttachBotInstall}
|
||||
title={name}
|
||||
textParts={lang('WebApp.AddToAttachmentText', name)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default BotAttachModal;
|
||||
export default memo(AttachBotInstallModal);
|
||||
18
src/components/main/AttachBotRecipientPicker.async.tsx
Normal file
18
src/components/main/AttachBotRecipientPicker.async.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import { Bundles } from '../../util/moduleLoader';
|
||||
import type { OwnProps } from './AttachBotRecipientPicker';
|
||||
|
||||
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||
|
||||
const AttachBotRecipientPickerAsync: FC<OwnProps> = (props) => {
|
||||
const { requestedAttachBotInChat } = props;
|
||||
const AttachBotRecipientPicker = useModuleLoader(
|
||||
Bundles.Extra, 'AttachBotRecipientPicker', !requestedAttachBotInChat,
|
||||
);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return AttachBotRecipientPicker ? <AttachBotRecipientPicker {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(AttachBotRecipientPickerAsync);
|
||||
53
src/components/main/AttachBotRecipientPicker.tsx
Normal file
53
src/components/main/AttachBotRecipientPicker.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import React, { memo, useCallback, useEffect } from '../../lib/teact/teact';
|
||||
import { getActions } from '../../global';
|
||||
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import type { GlobalState } from '../../global/types';
|
||||
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
|
||||
import RecipientPicker from '../common/RecipientPicker';
|
||||
|
||||
export type OwnProps = {
|
||||
requestedAttachBotInChat?: GlobalState['requestedAttachBotInChat'];
|
||||
};
|
||||
|
||||
const AttachBotRecipientPicker: FC<OwnProps> = ({
|
||||
requestedAttachBotInChat,
|
||||
}) => {
|
||||
const { cancelAttachBotInChat, callAttachBot } = getActions();
|
||||
const lang = useLang();
|
||||
|
||||
const isOpen = Boolean(requestedAttachBotInChat);
|
||||
const [isShown, markIsShown, unmarkIsShown] = useFlag();
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
markIsShown();
|
||||
}
|
||||
}, [isOpen, markIsShown]);
|
||||
|
||||
const { botId, filter, startParam } = requestedAttachBotInChat || {};
|
||||
|
||||
const handlePeerRecipient = useCallback((recipientId: string) => {
|
||||
callAttachBot({ botId: botId!, chatId: recipientId, startParam });
|
||||
cancelAttachBotInChat();
|
||||
}, [botId, callAttachBot, cancelAttachBotInChat, startParam]);
|
||||
|
||||
if (!isOpen && !isShown) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<RecipientPicker
|
||||
isOpen={isOpen}
|
||||
searchPlaceholder={lang('Search')}
|
||||
filter={filter}
|
||||
onSelectRecipient={handlePeerRecipient}
|
||||
onClose={cancelAttachBotInChat}
|
||||
onCloseAnimationEnd={unmarkIsShown}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(AttachBotRecipientPicker);
|
||||
@ -1,17 +0,0 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import { Bundles } from '../../util/moduleLoader';
|
||||
|
||||
import type { OwnProps } from './BotAttachModal';
|
||||
|
||||
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||
|
||||
const BotAttachModalAsync: FC<OwnProps> = (props) => {
|
||||
const { bot } = props;
|
||||
const BotAttachModal = useModuleLoader(Bundles.Extra, 'BotAttachModal', !bot);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return BotAttachModal ? <BotAttachModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(BotAttachModalAsync);
|
||||
16
src/components/main/DraftRecipientPicker.async.tsx
Normal file
16
src/components/main/DraftRecipientPicker.async.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import { Bundles } from '../../util/moduleLoader';
|
||||
import type { OwnProps } from './DraftRecipientPicker';
|
||||
|
||||
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||
|
||||
const DraftRecipientPickerAsync: FC<OwnProps> = (props) => {
|
||||
const { requestedDraft } = props;
|
||||
const DraftRecipientPicker = useModuleLoader(Bundles.Extra, 'DraftRecipientPicker', !requestedDraft);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return DraftRecipientPicker ? <DraftRecipientPicker {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(DraftRecipientPickerAsync);
|
||||
59
src/components/main/DraftRecipientPicker.tsx
Normal file
59
src/components/main/DraftRecipientPicker.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useCallback, useEffect,
|
||||
} from '../../lib/teact/teact';
|
||||
import { getActions } from '../../global';
|
||||
|
||||
import type { GlobalState } from '../../global/types';
|
||||
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
|
||||
import RecipientPicker from '../common/RecipientPicker';
|
||||
|
||||
export type OwnProps = {
|
||||
requestedDraft?: GlobalState['requestedDraft'];
|
||||
};
|
||||
|
||||
const DraftRecipientPicker: FC<OwnProps> = ({
|
||||
requestedDraft,
|
||||
}) => {
|
||||
const isOpen = Boolean(requestedDraft && !requestedDraft.chatId);
|
||||
const {
|
||||
openChatWithDraft,
|
||||
resetOpenChatWithDraft,
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const [isShown, markIsShown, unmarkIsShown] = useFlag();
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
markIsShown();
|
||||
}
|
||||
}, [isOpen, markIsShown]);
|
||||
|
||||
const handleSelectRecipient = useCallback((recipientId: string) => {
|
||||
openChatWithDraft({ chatId: recipientId, text: requestedDraft!.text });
|
||||
}, [openChatWithDraft, requestedDraft]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
resetOpenChatWithDraft();
|
||||
}, [resetOpenChatWithDraft]);
|
||||
|
||||
if (!isOpen && !isShown) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<RecipientPicker
|
||||
isOpen={isOpen}
|
||||
searchPlaceholder={lang('ForwardTo')}
|
||||
onSelectRecipient={handleSelectRecipient}
|
||||
onClose={handleClose}
|
||||
onCloseAnimationEnd={unmarkIsShown}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(DraftRecipientPicker);
|
||||
@ -1,16 +0,0 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import { Bundles } from '../../util/moduleLoader';
|
||||
import type { OwnProps } from './ForwardPicker';
|
||||
|
||||
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||
|
||||
const ForwardPickerAsync: FC<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const ForwardPicker = useModuleLoader(Bundles.Extra, 'ForwardPicker', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return ForwardPicker ? <ForwardPicker {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(ForwardPickerAsync);
|
||||
@ -1,154 +0,0 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, {
|
||||
useMemo, useState, memo, useRef, useCallback, useEffect,
|
||||
} from '../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||
|
||||
import type { ApiChat } from '../../api/types';
|
||||
import { MAIN_THREAD_ID } from '../../api/types';
|
||||
import type { GlobalState } from '../../global/types';
|
||||
|
||||
import {
|
||||
filterChatsByName,
|
||||
filterUsersByName,
|
||||
getCanPostInChat,
|
||||
sortChatIds,
|
||||
} from '../../global/helpers';
|
||||
import { unique } from '../../util/iteratees';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
|
||||
import ChatOrUserPicker from '../common/ChatOrUserPicker';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
chatsById: Record<string, ApiChat>;
|
||||
activeListIds?: string[];
|
||||
archivedListIds?: string[];
|
||||
pinnedIds?: string[];
|
||||
contactIds?: string[];
|
||||
currentUserId?: string;
|
||||
switchBotInline?: GlobalState['switchBotInline'];
|
||||
};
|
||||
|
||||
const ForwardPicker: FC<OwnProps & StateProps> = ({
|
||||
chatsById,
|
||||
activeListIds,
|
||||
archivedListIds,
|
||||
pinnedIds,
|
||||
contactIds,
|
||||
currentUserId,
|
||||
isOpen,
|
||||
switchBotInline,
|
||||
}) => {
|
||||
const {
|
||||
setForwardChatId,
|
||||
exitForwardMode,
|
||||
openChatWithText,
|
||||
resetSwitchBotInline,
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
const [filter, setFilter] = useState('');
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const filterRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const [isShown, markIsShown, unmarkIsShown] = useFlag();
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
markIsShown();
|
||||
}
|
||||
}, [isOpen, markIsShown]);
|
||||
|
||||
const chatAndContactIds = useMemo(() => {
|
||||
if (!isOpen) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let priorityIds = pinnedIds || [];
|
||||
if (currentUserId) {
|
||||
priorityIds = unique([currentUserId, ...priorityIds]);
|
||||
}
|
||||
|
||||
const chatIds = [
|
||||
...(activeListIds || []),
|
||||
...((filter && archivedListIds) || []),
|
||||
].filter((id) => {
|
||||
const chat = chatsById[id];
|
||||
|
||||
return chat && getCanPostInChat(chat, MAIN_THREAD_ID);
|
||||
});
|
||||
|
||||
// No need for expensive global updates on users, so we avoid them
|
||||
const usersById = getGlobal().users.byId;
|
||||
|
||||
return sortChatIds(unique([
|
||||
...filterChatsByName(lang, chatIds, chatsById, filter, currentUserId),
|
||||
...(contactIds ? filterUsersByName(contactIds, usersById, filter) : []),
|
||||
]), chatsById, undefined, priorityIds);
|
||||
}, [activeListIds, archivedListIds, chatsById, contactIds, currentUserId, filter, isOpen, lang, pinnedIds]);
|
||||
|
||||
const handleSelectUser = useCallback((userId: string) => {
|
||||
if (switchBotInline) {
|
||||
const text = `@${switchBotInline.botUsername} ${switchBotInline.query}`;
|
||||
openChatWithText({ chatId: userId, text });
|
||||
resetSwitchBotInline();
|
||||
} else {
|
||||
setForwardChatId({ id: userId });
|
||||
}
|
||||
}, [openChatWithText, resetSwitchBotInline, setForwardChatId, switchBotInline]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
exitForwardMode();
|
||||
resetSwitchBotInline();
|
||||
}, [exitForwardMode, resetSwitchBotInline]);
|
||||
|
||||
const renderingChatAndContactIds = useCurrentOrPrev(chatAndContactIds, true)!;
|
||||
|
||||
if (!isOpen && !isShown) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<ChatOrUserPicker
|
||||
currentUserId={currentUserId}
|
||||
isOpen={isOpen}
|
||||
chatOrUserIds={renderingChatAndContactIds}
|
||||
filterRef={filterRef}
|
||||
filterPlaceholder={lang('ForwardTo')}
|
||||
filter={filter}
|
||||
onFilterChange={setFilter}
|
||||
onSelectChatOrUser={handleSelectUser}
|
||||
onClose={handleClose}
|
||||
onCloseAnimationEnd={unmarkIsShown}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const {
|
||||
chats: {
|
||||
byId: chatsById,
|
||||
listIds,
|
||||
orderedPinnedIds,
|
||||
},
|
||||
currentUserId,
|
||||
switchBotInline,
|
||||
} = global;
|
||||
|
||||
return {
|
||||
chatsById,
|
||||
activeListIds: listIds.active,
|
||||
archivedListIds: listIds.archived,
|
||||
pinnedIds: orderedPinnedIds.active,
|
||||
contactIds: global.contactList?.userIds,
|
||||
currentUserId,
|
||||
switchBotInline,
|
||||
};
|
||||
},
|
||||
)(ForwardPicker));
|
||||
16
src/components/main/ForwardRecipientPicker.async.tsx
Normal file
16
src/components/main/ForwardRecipientPicker.async.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import { Bundles } from '../../util/moduleLoader';
|
||||
import type { OwnProps } from './ForwardRecipientPicker';
|
||||
|
||||
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||
|
||||
const ForwardRecipientPickerAsync: FC<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const ForwardRecipientPicker = useModuleLoader(Bundles.Extra, 'ForwardRecipientPicker', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return ForwardRecipientPicker ? <ForwardRecipientPicker {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(ForwardRecipientPickerAsync);
|
||||
56
src/components/main/ForwardRecipientPicker.tsx
Normal file
56
src/components/main/ForwardRecipientPicker.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, {
|
||||
memo, useCallback, useEffect,
|
||||
} from '../../lib/teact/teact';
|
||||
import { getActions } from '../../global';
|
||||
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useFlag from '../../hooks/useFlag';
|
||||
|
||||
import RecipientPicker from '../common/RecipientPicker';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen: boolean;
|
||||
};
|
||||
|
||||
const ForwardRecipientPicker: FC<OwnProps> = ({
|
||||
isOpen,
|
||||
}) => {
|
||||
const {
|
||||
setForwardChatId,
|
||||
exitForwardMode,
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const [isShown, markIsShown, unmarkIsShown] = useFlag();
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
markIsShown();
|
||||
}
|
||||
}, [isOpen, markIsShown]);
|
||||
|
||||
const handleSelectRecipient = useCallback((recipientId: string) => {
|
||||
setForwardChatId({ id: recipientId });
|
||||
}, [setForwardChatId]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
exitForwardMode();
|
||||
}, [exitForwardMode]);
|
||||
|
||||
if (!isOpen && !isShown) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return (
|
||||
<RecipientPicker
|
||||
isOpen={isOpen}
|
||||
searchPlaceholder={lang('ForwardTo')}
|
||||
onSelectRecipient={handleSelectRecipient}
|
||||
onClose={handleClose}
|
||||
onCloseAnimationEnd={unmarkIsShown}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ForwardRecipientPicker);
|
||||
@ -52,7 +52,7 @@ import DownloadManager from './DownloadManager';
|
||||
import GameModal from './GameModal';
|
||||
import Notifications from './Notifications.async';
|
||||
import Dialogs from './Dialogs.async';
|
||||
import ForwardPicker from './ForwardPicker.async';
|
||||
import ForwardRecipientPicker from './ForwardRecipientPicker.async';
|
||||
import SafeLinkModal from './SafeLinkModal.async';
|
||||
import HistoryCalendar from './HistoryCalendar.async';
|
||||
import GroupCall from '../calls/group/GroupCall.async';
|
||||
@ -63,7 +63,7 @@ import NewContactModal from './NewContactModal.async';
|
||||
import RatePhoneCallModal from '../calls/phone/RatePhoneCallModal.async';
|
||||
import WebAppModal from './WebAppModal.async';
|
||||
import BotTrustModal from './BotTrustModal.async';
|
||||
import BotAttachModal from './BotAttachModal.async';
|
||||
import AttachBotInstallModal from './AttachBotInstallModal.async';
|
||||
import ConfettiContainer from './ConfettiContainer';
|
||||
import UrlAuthModal from './UrlAuthModal.async';
|
||||
import PremiumMainModal from './premium/PremiumMainModal.async';
|
||||
@ -72,6 +72,8 @@ import ReceiptModal from '../payment/ReceiptModal.async';
|
||||
import PremiumLimitReachedModal from './premium/common/PremiumLimitReachedModal.async';
|
||||
import DeleteFolderDialog from './DeleteFolderDialog.async';
|
||||
import CustomEmojiSetsModal from '../common/CustomEmojiSetsModal.async';
|
||||
import DraftRecipientPicker from './DraftRecipientPicker.async';
|
||||
import AttachBotRecipientPicker from './AttachBotRecipientPicker.async';
|
||||
|
||||
import './Main.scss';
|
||||
|
||||
@ -109,7 +111,9 @@ type StateProps = {
|
||||
isPremiumModalOpen?: boolean;
|
||||
botTrustRequest?: GlobalState['botTrustRequest'];
|
||||
botTrustRequestBot?: ApiUser;
|
||||
botAttachRequestBot?: ApiUser;
|
||||
attachBotToInstall?: ApiUser;
|
||||
requestedAttachBotInChat?: GlobalState['requestedAttachBotInChat'];
|
||||
requestedDraft?: GlobalState['requestedDraft'];
|
||||
currentUser?: ApiUser;
|
||||
urlAuth?: GlobalState['urlAuth'];
|
||||
limitReached?: ApiLimitTypeWithModal;
|
||||
@ -158,7 +162,9 @@ const Main: FC<StateProps> = ({
|
||||
isRatePhoneCallModalOpen,
|
||||
botTrustRequest,
|
||||
botTrustRequestBot,
|
||||
botAttachRequestBot,
|
||||
attachBotToInstall,
|
||||
requestedAttachBotInChat,
|
||||
requestedDraft,
|
||||
webApp,
|
||||
currentUser,
|
||||
urlAuth,
|
||||
@ -186,7 +192,7 @@ const Main: FC<StateProps> = ({
|
||||
closeCustomEmojiSets,
|
||||
checkVersionNotification,
|
||||
loadAppConfig,
|
||||
loadAttachMenuBots,
|
||||
loadAttachBots,
|
||||
loadContactList,
|
||||
loadCustomEmojis,
|
||||
closePaymentModal,
|
||||
@ -219,14 +225,14 @@ const Main: FC<StateProps> = ({
|
||||
loadNotificationExceptions();
|
||||
loadTopInlineBots();
|
||||
loadEmojiKeywords({ language: BASE_EMOJI_KEYWORD_LANG });
|
||||
loadAttachMenuBots();
|
||||
loadAttachBots();
|
||||
loadContactList();
|
||||
loadPremiumGifts();
|
||||
checkAppVersion();
|
||||
}
|
||||
}, [
|
||||
lastSyncTime, loadAnimatedEmojis, loadEmojiKeywords, loadNotificationExceptions, loadNotificationSettings,
|
||||
loadTopInlineBots, updateIsOnline, loadAvailableReactions, loadAppConfig, loadAttachMenuBots, loadContactList,
|
||||
loadTopInlineBots, updateIsOnline, loadAvailableReactions, loadAppConfig, loadAttachBots, loadContactList,
|
||||
loadPremiumGifts, checkAppVersion,
|
||||
]);
|
||||
|
||||
@ -423,7 +429,8 @@ const Main: FC<StateProps> = ({
|
||||
<MiddleColumn />
|
||||
<RightColumn />
|
||||
<MediaViewer isOpen={isMediaViewerOpen} />
|
||||
<ForwardPicker isOpen={isForwardModalOpen} />
|
||||
<ForwardRecipientPicker isOpen={isForwardModalOpen} />
|
||||
<DraftRecipientPicker requestedDraft={requestedDraft} />
|
||||
<Notifications isOpen={hasNotifications} />
|
||||
<Dialogs isOpen={hasDialogs} />
|
||||
{audioMessage && <AudioPlayer key={audioMessage.id} message={audioMessage} noUi />}
|
||||
@ -454,7 +461,8 @@ const Main: FC<StateProps> = ({
|
||||
<UnreadCount isForAppBadge />
|
||||
<RatePhoneCallModal isOpen={isRatePhoneCallModalOpen} />
|
||||
<BotTrustModal bot={botTrustRequestBot} type={botTrustRequest?.type} />
|
||||
<BotAttachModal bot={botAttachRequestBot} />
|
||||
<AttachBotInstallModal bot={attachBotToInstall} />
|
||||
<AttachBotRecipientPicker requestedAttachBotInChat={requestedAttachBotInChat} />
|
||||
<MessageListHistoryHandler />
|
||||
{isPremiumModalOpen && <PremiumMainModal isOpen={isPremiumModalOpen} />}
|
||||
<PremiumLimitReachedModal limit={limitReached} />
|
||||
@ -494,8 +502,19 @@ export default memo(withGlobal(
|
||||
animationLevel, language, wasTimeFormatSetManually,
|
||||
},
|
||||
},
|
||||
connectionState,
|
||||
botTrustRequest,
|
||||
botAttachRequest,
|
||||
requestedAttachBotInstall,
|
||||
requestedAttachBotInChat,
|
||||
requestedDraft,
|
||||
urlAuth,
|
||||
webApp,
|
||||
safeLinkModalUrl,
|
||||
authState,
|
||||
lastSyncTime,
|
||||
openedStickerSetShortName,
|
||||
openedCustomEmojiSetIds,
|
||||
shouldSkipHistoryAnimations,
|
||||
} = global;
|
||||
const { chatId: audioChatId, messageId: audioMessageId } = global.audioPlayer;
|
||||
const audioMessage = audioChatId && audioMessageId
|
||||
@ -507,9 +526,9 @@ export default memo(withGlobal(
|
||||
const currentUser = global.currentUserId ? selectUser(global, global.currentUserId) : undefined;
|
||||
|
||||
return {
|
||||
connectionState: global.connectionState,
|
||||
authState: global.authState,
|
||||
lastSyncTime: global.lastSyncTime,
|
||||
connectionState,
|
||||
authState,
|
||||
lastSyncTime,
|
||||
isLeftColumnOpen: global.isLeftColumnShown,
|
||||
isRightColumnOpen: selectIsRightColumnShown(global),
|
||||
isMediaViewerOpen: selectIsMediaViewerOpen(global),
|
||||
@ -517,11 +536,11 @@ export default memo(withGlobal(
|
||||
hasNotifications: Boolean(global.notifications.length),
|
||||
hasDialogs: Boolean(global.dialogs.length),
|
||||
audioMessage,
|
||||
safeLinkModalUrl: global.safeLinkModalUrl,
|
||||
safeLinkModalUrl,
|
||||
isHistoryCalendarOpen: Boolean(global.historyCalendarSelectedAt),
|
||||
shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations,
|
||||
openedStickerSetShortName: global.openedStickerSetShortName,
|
||||
openedCustomEmojiSetIds: global.openedCustomEmojiSetIds,
|
||||
shouldSkipHistoryAnimations,
|
||||
openedStickerSetShortName,
|
||||
openedCustomEmojiSetIds,
|
||||
isServiceChatReady: selectIsServiceChatReady(global),
|
||||
activeGroupCallId: global.groupCalls.activeGroupCallId,
|
||||
animationLevel,
|
||||
@ -537,15 +556,17 @@ export default memo(withGlobal(
|
||||
isRatePhoneCallModalOpen: Boolean(global.ratingPhoneCall),
|
||||
botTrustRequest,
|
||||
botTrustRequestBot: botTrustRequest && selectUser(global, botTrustRequest.botId),
|
||||
botAttachRequestBot: botAttachRequest && selectUser(global, botAttachRequest.botId),
|
||||
webApp: global.webApp,
|
||||
attachBotToInstall: requestedAttachBotInstall && selectUser(global, requestedAttachBotInstall.botId),
|
||||
requestedAttachBotInChat,
|
||||
webApp,
|
||||
currentUser,
|
||||
urlAuth: global.urlAuth,
|
||||
urlAuth,
|
||||
isPremiumModalOpen: global.premiumModal?.isOpen,
|
||||
limitReached: global.limitReachedModal?.limit,
|
||||
isPaymentModalOpen: global.payment.isPaymentModalOpen,
|
||||
isReceiptModalOpen: Boolean(global.payment.receipt),
|
||||
deleteFolderDialogId: global.deleteFolderDialogModal,
|
||||
requestedDraft,
|
||||
};
|
||||
},
|
||||
)(Main));
|
||||
|
||||
@ -4,7 +4,7 @@ import React, {
|
||||
import { getActions, withGlobal } from '../../global';
|
||||
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import type { ApiAttachMenuBot, ApiChat, ApiUser } from '../../api/types';
|
||||
import type { ApiAttachBot, ApiChat, ApiUser } from '../../api/types';
|
||||
import type { GlobalState } from '../../global/types';
|
||||
import type { ThemeKey } from '../../types';
|
||||
import type { PopupOptions, WebAppInboundEvent } from './hooks/useWebAppFrame';
|
||||
@ -48,7 +48,7 @@ export type OwnProps = {
|
||||
type StateProps = {
|
||||
chat?: ApiChat;
|
||||
bot?: ApiUser;
|
||||
attachMenuBot?: ApiAttachMenuBot;
|
||||
attachBot?: ApiAttachBot;
|
||||
theme?: ThemeKey;
|
||||
isPaymentModalOpen?: boolean;
|
||||
paymentStatus?: GlobalState['payment']['status'];
|
||||
@ -78,7 +78,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
webApp,
|
||||
chat,
|
||||
bot,
|
||||
attachMenuBot,
|
||||
attachBot,
|
||||
theme,
|
||||
isPaymentModalOpen,
|
||||
paymentStatus,
|
||||
@ -87,7 +87,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
closeWebApp,
|
||||
sendWebViewData,
|
||||
prolongWebView,
|
||||
toggleBotInAttachMenu,
|
||||
toggleAttachBot,
|
||||
openTelegramLink,
|
||||
openChat,
|
||||
openInvoice,
|
||||
@ -279,11 +279,11 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
}, [isPaymentModalOpen, paymentStatus, sendEvent, setWebAppPaymentSlug, webApp] as const);
|
||||
|
||||
const handleToggleClick = useCallback(() => {
|
||||
toggleBotInAttachMenu({
|
||||
toggleAttachBot({
|
||||
botId: bot!.id,
|
||||
isEnabled: !attachMenuBot,
|
||||
isEnabled: !attachBot,
|
||||
});
|
||||
}, [bot, attachMenuBot, toggleBotInAttachMenu]);
|
||||
}, [bot, attachBot, toggleAttachBot]);
|
||||
|
||||
const handleBackClick = useCallback(() => {
|
||||
if (isBackButtonVisible) {
|
||||
@ -353,16 +353,16 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
<MenuItem icon="bots" onClick={openBotChat}>{lang('BotWebViewOpenBot')}</MenuItem>
|
||||
)}
|
||||
<MenuItem icon="reload" onClick={handleRefreshClick}>{lang('WebApp.ReloadPage')}</MenuItem>
|
||||
{bot?.isAttachMenuBot && (
|
||||
{bot?.isAttachBot && (
|
||||
<MenuItem
|
||||
icon={attachMenuBot ? 'stop' : 'install'}
|
||||
icon={attachBot ? 'stop' : 'install'}
|
||||
onClick={handleToggleClick}
|
||||
destructive={Boolean(attachMenuBot)}
|
||||
destructive={Boolean(attachBot)}
|
||||
>
|
||||
{lang(attachMenuBot ? 'WebApp.RemoveBot' : 'WebApp.AddToAttachmentAdd')}
|
||||
{lang(attachBot ? 'WebApp.RemoveBot' : 'WebApp.AddToAttachmentAdd')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{attachMenuBot?.hasSettings && (
|
||||
{attachBot?.hasSettings && (
|
||||
<MenuItem icon="settings" onClick={handleSettingsButtonClick}>
|
||||
{lang('Settings')}
|
||||
</MenuItem>
|
||||
@ -371,7 +371,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
</div>
|
||||
);
|
||||
}, [
|
||||
lang, handleBackClick, bot, MoreMenuButton, chat, openBotChat, handleRefreshClick, attachMenuBot,
|
||||
lang, handleBackClick, bot, MoreMenuButton, chat, openBotChat, handleRefreshClick, attachBot,
|
||||
handleToggleClick, handleSettingsButtonClick, isBackButtonVisible, headerColor, backButtonClassName,
|
||||
]);
|
||||
|
||||
@ -494,14 +494,14 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { webApp }): StateProps => {
|
||||
const { botId } = webApp || {};
|
||||
const attachMenuBot = botId ? global.attachMenu.bots[botId] : undefined;
|
||||
const attachBot = botId ? global.attachMenu.bots[botId] : undefined;
|
||||
const bot = botId ? selectUser(global, botId) : undefined;
|
||||
const chat = selectCurrentChat(global);
|
||||
const theme = selectTheme(global);
|
||||
const { isPaymentModalOpen, status } = global.payment;
|
||||
|
||||
return {
|
||||
attachMenuBot,
|
||||
attachBot,
|
||||
bot,
|
||||
chat,
|
||||
theme,
|
||||
|
||||
@ -39,7 +39,7 @@ type StateProps = {
|
||||
canReportMessages?: boolean;
|
||||
canDownloadMessages?: boolean;
|
||||
hasProtectedMessage?: boolean;
|
||||
isForwardModalOpen?: boolean;
|
||||
isAnyModalOpen?: boolean;
|
||||
selectedMessageIds?: number[];
|
||||
};
|
||||
|
||||
@ -53,7 +53,7 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
||||
canReportMessages,
|
||||
canDownloadMessages,
|
||||
hasProtectedMessage,
|
||||
isForwardModalOpen,
|
||||
isAnyModalOpen,
|
||||
selectedMessageIds,
|
||||
}) => {
|
||||
const {
|
||||
@ -70,7 +70,7 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
||||
|
||||
useCopySelectedMessages(Boolean(isActive), copySelectedMessages);
|
||||
useEffect(() => {
|
||||
return isActive && !isDeleteModalOpen && !isReportModalOpen && !isForwardModalOpen
|
||||
return isActive && !isDeleteModalOpen && !isReportModalOpen && !isAnyModalOpen
|
||||
? captureKeyboardListeners({
|
||||
onBackspace: canDeleteMessages ? openDeleteModal : undefined,
|
||||
onDelete: canDeleteMessages ? openDeleteModal : undefined,
|
||||
@ -78,7 +78,7 @@ const MessageSelectToolbar: FC<OwnProps & StateProps> = ({
|
||||
})
|
||||
: undefined;
|
||||
}, [
|
||||
isActive, isDeleteModalOpen, isReportModalOpen, openDeleteModal, exitMessageSelectMode, isForwardModalOpen,
|
||||
isActive, isDeleteModalOpen, isReportModalOpen, openDeleteModal, exitMessageSelectMode, isAnyModalOpen,
|
||||
canDeleteMessages,
|
||||
]);
|
||||
|
||||
@ -183,6 +183,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
const { messageIds: selectedMessageIds } = global.selectedMessages || {};
|
||||
const hasProtectedMessage = chatId ? selectHasProtectedMessage(global, chatId, selectedMessageIds) : false;
|
||||
const isForwardModalOpen = global.forwardMessages.isModalShown;
|
||||
const isAnyModalOpen = Boolean(isForwardModalOpen || global.requestedDraft
|
||||
|| global.requestedAttachBotInChat || global.requestedAttachBotInstall);
|
||||
|
||||
return {
|
||||
isSchedule,
|
||||
@ -192,7 +194,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
canDownloadMessages: canDownload,
|
||||
selectedMessageIds,
|
||||
hasProtectedMessage,
|
||||
isForwardModalOpen,
|
||||
isAnyModalOpen,
|
||||
};
|
||||
},
|
||||
)(MessageSelectToolbar));
|
||||
|
||||
@ -10,7 +10,7 @@ import useMedia from '../../../hooks/useMedia';
|
||||
import { getDocumentMediaHash } from '../../../global/helpers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
|
||||
import styles from './AttachmentMenuBotIcon.module.scss';
|
||||
import styles from './AttachBotIcon.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
icon: ApiDocument;
|
||||
@ -22,7 +22,7 @@ const DARK_THEME_COLOR = 'rgb(170, 170, 170)';
|
||||
const LIGHT_THEME_COLOR = 'rgb(112, 117, 121)';
|
||||
const COLOR_REPLACE_PATTERN = /#fff/gi;
|
||||
|
||||
const AttachmentMenuBotIcon: FC<OwnProps> = ({
|
||||
const AttachBotIcon: FC<OwnProps> = ({
|
||||
icon, theme,
|
||||
}) => {
|
||||
const mediaData = useMedia(getDocumentMediaHash(icon), false, ApiMediaFormat.Text);
|
||||
@ -48,4 +48,4 @@ const AttachmentMenuBotIcon: FC<OwnProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(AttachmentMenuBotIcon);
|
||||
export default memo(AttachBotIcon);
|
||||
@ -5,7 +5,7 @@ import React, {
|
||||
import { getActions } from '../../../global';
|
||||
|
||||
import type { IAnchorPosition, ISettings } from '../../../types';
|
||||
import type { ApiAttachMenuBot } from '../../../api/types';
|
||||
import type { ApiAttachBot } from '../../../api/types';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
@ -13,24 +13,24 @@ import useLang from '../../../hooks/useLang';
|
||||
import Portal from '../../ui/Portal';
|
||||
import Menu from '../../ui/Menu';
|
||||
import MenuItem from '../../ui/MenuItem';
|
||||
import AttachmentMenuBotIcon from './AttachmentMenuBotIcon';
|
||||
import AttachBotIcon from './AttachBotIcon';
|
||||
|
||||
type OwnProps = {
|
||||
bot: ApiAttachMenuBot;
|
||||
bot: ApiAttachBot;
|
||||
theme: ISettings['theme'];
|
||||
chatId: string;
|
||||
onMenuOpened: VoidFunction;
|
||||
onMenuClosed: VoidFunction;
|
||||
};
|
||||
|
||||
const AttachmentMenuBotItem: FC<OwnProps> = ({
|
||||
const AttachBotItem: FC<OwnProps> = ({
|
||||
bot,
|
||||
theme,
|
||||
chatId,
|
||||
onMenuOpened,
|
||||
onMenuClosed,
|
||||
}) => {
|
||||
const { callAttachMenuBot, toggleBotInAttachMenu } = getActions();
|
||||
const { callAttachBot, toggleAttachBot } = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
@ -59,19 +59,19 @@ const AttachmentMenuBotItem: FC<OwnProps> = ({
|
||||
}, []);
|
||||
|
||||
const handleRemoveBot = useCallback(() => {
|
||||
toggleBotInAttachMenu({
|
||||
toggleAttachBot({
|
||||
botId: bot.id,
|
||||
isEnabled: false,
|
||||
});
|
||||
}, [bot.id, toggleBotInAttachMenu]);
|
||||
}, [bot.id, toggleAttachBot]);
|
||||
|
||||
return (
|
||||
<MenuItem
|
||||
key={bot.id}
|
||||
customIcon={icon && <AttachmentMenuBotIcon icon={icon} theme={theme} />}
|
||||
customIcon={icon && <AttachBotIcon icon={icon} theme={theme} />}
|
||||
icon={!icon ? 'bots' : undefined}
|
||||
// eslint-disable-next-line react/jsx-no-bind
|
||||
onClick={() => callAttachMenuBot({
|
||||
onClick={() => callAttachBot({
|
||||
botId: bot.id,
|
||||
chatId,
|
||||
})}
|
||||
@ -98,4 +98,4 @@ const AttachmentMenuBotItem: FC<OwnProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(AttachmentMenuBotItem);
|
||||
export default memo(AttachBotItem);
|
||||
@ -18,7 +18,7 @@ import useFlag from '../../../hooks/useFlag';
|
||||
import ResponsiveHoverButton from '../../ui/ResponsiveHoverButton';
|
||||
import Menu from '../../ui/Menu';
|
||||
import MenuItem from '../../ui/MenuItem';
|
||||
import AttachmentMenuBotItem from './AttachmentMenuBotItem';
|
||||
import AttachBotItem from './AttachBotItem';
|
||||
|
||||
import './AttachMenu.scss';
|
||||
|
||||
@ -28,7 +28,7 @@ export type OwnProps = {
|
||||
canAttachMedia: boolean;
|
||||
canAttachPolls: boolean;
|
||||
isScheduled?: boolean;
|
||||
attachMenuBots: GlobalState['attachMenu']['bots'];
|
||||
attachBots: GlobalState['attachMenu']['bots'];
|
||||
peerType?: ApiAttachMenuPeerType;
|
||||
onFileSelect: (files: File[], isQuick: boolean) => void;
|
||||
onPollCreate: () => void;
|
||||
@ -40,7 +40,7 @@ const AttachMenu: FC<OwnProps> = ({
|
||||
isButtonVisible,
|
||||
canAttachMedia,
|
||||
canAttachPolls,
|
||||
attachMenuBots,
|
||||
attachBots,
|
||||
peerType,
|
||||
isScheduled,
|
||||
onFileSelect,
|
||||
@ -85,14 +85,14 @@ const AttachMenu: FC<OwnProps> = ({
|
||||
}, [handleFileSelect]);
|
||||
|
||||
const bots = useMemo(() => {
|
||||
return Object.values(attachMenuBots).filter((bot) => {
|
||||
return Object.values(attachBots).filter((bot) => {
|
||||
if (!peerType) return false;
|
||||
if (peerType === 'bot' && bot.id === chatId && bot.peerTypes.includes('self')) {
|
||||
if (peerType === 'bots' && bot.id === chatId && bot.peerTypes.includes('self')) {
|
||||
return true;
|
||||
}
|
||||
return bot.peerTypes.includes(peerType);
|
||||
});
|
||||
}, [attachMenuBots, chatId, peerType]);
|
||||
}, [attachBots, chatId, peerType]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
@ -146,7 +146,7 @@ const AttachMenu: FC<OwnProps> = ({
|
||||
)}
|
||||
|
||||
{canAttachMedia && !isScheduled && bots.map((bot) => (
|
||||
<AttachmentMenuBotItem
|
||||
<AttachBotItem
|
||||
bot={bot}
|
||||
chatId={chatId}
|
||||
theme={theme}
|
||||
|
||||
@ -54,7 +54,7 @@ import {
|
||||
selectTheme,
|
||||
selectCurrentMessageList,
|
||||
selectIsCurrentUserPremium,
|
||||
selectAttachMenuPeerType,
|
||||
selectChatType,
|
||||
} from '../../../global/selectors';
|
||||
import {
|
||||
getAllowedAttachmentOptions,
|
||||
@ -169,7 +169,7 @@ type StateProps =
|
||||
sendAsId?: string;
|
||||
editingDraft?: ApiFormattedText;
|
||||
requestedText?: string;
|
||||
attachMenuBots: GlobalState['attachMenu']['bots'];
|
||||
attachBots: GlobalState['attachMenu']['bots'];
|
||||
attachMenuPeerType?: ApiAttachMenuPeerType;
|
||||
theme: ISettings['theme'];
|
||||
fileSizeLimit: number;
|
||||
@ -249,7 +249,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
editingDraft,
|
||||
requestedText,
|
||||
botMenuButton,
|
||||
attachMenuBots,
|
||||
attachBots,
|
||||
attachMenuPeerType,
|
||||
theme,
|
||||
}) => {
|
||||
@ -268,8 +268,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
sendInlineBotResult,
|
||||
loadSendAs,
|
||||
loadFullChat,
|
||||
resetOpenChatWithText,
|
||||
callAttachMenuBot,
|
||||
resetOpenChatWithDraft,
|
||||
callAttachBot,
|
||||
openLimitReachedModal,
|
||||
showNotification,
|
||||
} = getActions();
|
||||
@ -674,10 +674,10 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const handleClickBotMenu = useCallback(() => {
|
||||
if (botMenuButton?.type !== 'webApp') return;
|
||||
callAttachMenuBot({
|
||||
callAttachBot({
|
||||
botId: chatId, chatId, isFromBotMenu: true, url: botMenuButton.url,
|
||||
});
|
||||
}, [botMenuButton, callAttachMenuBot, chatId]);
|
||||
}, [botMenuButton, callAttachBot, chatId]);
|
||||
|
||||
const handleActivateBotCommandMenu = useCallback(() => {
|
||||
closeSymbolMenu();
|
||||
@ -727,13 +727,13 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
useEffect(() => {
|
||||
if (requestedText) {
|
||||
setHtml(requestedText);
|
||||
resetOpenChatWithText();
|
||||
resetOpenChatWithDraft();
|
||||
requestAnimationFrame(() => {
|
||||
const messageInput = document.getElementById(EDITABLE_INPUT_ID)!;
|
||||
focusEditableElement(messageInput, true);
|
||||
});
|
||||
}
|
||||
}, [requestedText, resetOpenChatWithText]);
|
||||
}, [requestedText, resetOpenChatWithDraft]);
|
||||
|
||||
const handleStickerSelect = useCallback((
|
||||
sticker: ApiSticker, isSilent?: boolean, isScheduleRequested?: boolean, shouldPreserveInput = false,
|
||||
@ -1211,7 +1211,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
onFileSelect={handleFileSelect}
|
||||
onPollCreate={openPollModal}
|
||||
isScheduled={shouldSchedule}
|
||||
attachMenuBots={attachMenuBots}
|
||||
attachBots={attachBots}
|
||||
peerType={attachMenuPeerType}
|
||||
theme={theme}
|
||||
/>
|
||||
@ -1377,8 +1377,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
sendAsId,
|
||||
editingDraft,
|
||||
requestedText,
|
||||
attachMenuBots: global.attachMenu.bots,
|
||||
attachMenuPeerType: selectAttachMenuPeerType(global, chatId),
|
||||
attachBots: global.attachMenu.bots,
|
||||
attachMenuPeerType: selectChatType(global, chatId),
|
||||
theme: selectTheme(global),
|
||||
fileSizeLimit: selectCurrentLimit(global, 'uploadMaxFileparts') * MAX_UPLOAD_FILEPART_SIZE,
|
||||
captionLimit: selectCurrentLimit(global, 'captionLength'),
|
||||
|
||||
@ -54,10 +54,12 @@ export default function useInlineBotTooltip(
|
||||
}, [query, isAllowed, queryInlineBot, chatId, usernameLowered]);
|
||||
|
||||
const loadMore = useCallback(() => {
|
||||
queryInlineBot({
|
||||
chatId, username: usernameLowered, query, offset,
|
||||
});
|
||||
}, [offset, chatId, query, queryInlineBot, usernameLowered]);
|
||||
if (isAllowed && usernameLowered && chatId) {
|
||||
queryInlineBot({
|
||||
chatId, username: usernameLowered, query, offset,
|
||||
});
|
||||
}
|
||||
}, [isAllowed, usernameLowered, chatId, queryInlineBot, query, offset]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isAllowed && botId && (switchPm || (results?.length))) {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
import React, {
|
||||
useMemo, useState, memo, useRef, useCallback,
|
||||
useMemo, useState, memo, useCallback,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
|
||||
@ -33,9 +33,7 @@ const RemoveGroupUserModal: FC<OwnProps & StateProps> = ({
|
||||
} = getActions();
|
||||
|
||||
const lang = useLang();
|
||||
const [filter, setFilter] = useState('');
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const filterRef = useRef<HTMLInputElement>(null);
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
const usersId = useMemo(() => {
|
||||
const availableMemberIds = (chat.fullInfo?.members || [])
|
||||
@ -49,8 +47,8 @@ const RemoveGroupUserModal: FC<OwnProps & StateProps> = ({
|
||||
// No need for expensive global updates on users, so we avoid them
|
||||
const usersById = getGlobal().users.byId;
|
||||
|
||||
return filterUsersByName(availableMemberIds, usersById, filter);
|
||||
}, [chat.fullInfo?.members, currentUserId, filter]);
|
||||
return filterUsersByName(availableMemberIds, usersById, search);
|
||||
}, [chat.fullInfo?.members, currentUserId, search]);
|
||||
|
||||
const handleRemoveUser = useCallback((userId: string) => {
|
||||
deleteChatMember({ chatId: chat.id, userId });
|
||||
@ -61,10 +59,9 @@ const RemoveGroupUserModal: FC<OwnProps & StateProps> = ({
|
||||
<ChatOrUserPicker
|
||||
isOpen={isOpen}
|
||||
chatOrUserIds={usersId}
|
||||
filterRef={filterRef}
|
||||
filterPlaceholder={lang('ChannelBlockUser')}
|
||||
filter={filter}
|
||||
onFilterChange={setFilter}
|
||||
searchPlaceholder={lang('ChannelBlockUser')}
|
||||
search={search}
|
||||
onSearchChange={setSearch}
|
||||
loadMore={loadMoreMembers}
|
||||
onSelectChatOrUser={handleRemoveUser}
|
||||
onClose={onClose}
|
||||
|
||||
@ -193,7 +193,7 @@ export const CONTENT_NOT_SUPPORTED = 'The message is not supported on this versi
|
||||
// eslint-disable-next-line max-len
|
||||
export const RE_LINK_TEMPLATE = '((ftp|https?):\\/\\/)?((www\\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\\.[a-zA-Z0-9()]{1,63})\\b([-a-zA-Z0-9()@:%_+.~#?&/=]*)';
|
||||
export const RE_MENTION_TEMPLATE = '(@[\\w\\d_-]+)';
|
||||
export const RE_TG_LINK = /^tg:(\/\/)?([?=&\d\w_-]+)?/;
|
||||
export const RE_TG_LINK = /^tg:(\/\/)?/;
|
||||
export const RE_TME_LINK = /^(https?:\/\/)?([-a-zA-Z0-9@:%_+~#=]{1,32}\.)?t\.me/;
|
||||
export const RE_TELEGRAM_LINK = /^(https?:\/\/)?telegram\.org\//;
|
||||
export const TME_LINK_PREFIX = 'https://t.me/';
|
||||
@ -201,6 +201,8 @@ export const TME_LINK_PREFIX = 'https://t.me/';
|
||||
// eslint-disable-next-line max-len
|
||||
export const COUNTRIES_WITH_12H_TIME_FORMAT = new Set(['AU', 'BD', 'CA', 'CO', 'EG', 'HN', 'IE', 'IN', 'JO', 'MX', 'MY', 'NI', 'NZ', 'PH', 'PK', 'SA', 'SV', 'US']);
|
||||
|
||||
export const API_CHAT_TYPES = ['bots', 'channels', 'chats', 'users'] as const;
|
||||
|
||||
// MTProto constants
|
||||
export const SERVICE_NOTIFICATIONS_USER_ID = '777000';
|
||||
export const REPLIES_USER_ID = '1271266957'; // TODO For Test connection ID must be equal to 708513
|
||||
|
||||
@ -3,7 +3,7 @@ import {
|
||||
} from '../../index';
|
||||
|
||||
import type {
|
||||
ApiChat, ApiContact, ApiUrlAuthResult, ApiUser,
|
||||
ApiChat, ApiChatType, ApiContact, ApiUrlAuthResult, ApiUser,
|
||||
} from '../../../api/types';
|
||||
import type { InlineBotSettings } from '../../../types';
|
||||
|
||||
@ -287,27 +287,11 @@ addActionHandler('switchBotInline', (global, actions, payload) => {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const text = `@${botSender.username} ${query}`;
|
||||
|
||||
if (isSamePeer) {
|
||||
actions.openChatWithText({ chatId: chat.id, text });
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
...global,
|
||||
switchBotInline: {
|
||||
query,
|
||||
botUsername: botSender.username,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
addActionHandler('resetSwitchBotInline', (global) => {
|
||||
return {
|
||||
...global,
|
||||
switchBotInline: undefined,
|
||||
};
|
||||
actions.openChatWithDraft({
|
||||
text: `@${botSender.username} ${query}`,
|
||||
chatId: isSamePeer ? chat.id : undefined,
|
||||
});
|
||||
return undefined;
|
||||
});
|
||||
|
||||
addActionHandler('sendInlineBotResult', (global, actions, payload) => {
|
||||
@ -557,28 +541,28 @@ addActionHandler('markBotTrusted', (global, actions, payload) => {
|
||||
}
|
||||
});
|
||||
|
||||
addActionHandler('loadAttachMenuBots', async (global, actions, payload) => {
|
||||
addActionHandler('loadAttachBots', async (global, actions, payload) => {
|
||||
const { hash } = payload || {};
|
||||
await loadAttachMenuBots(hash);
|
||||
await loadAttachBots(hash);
|
||||
});
|
||||
|
||||
addActionHandler('toggleBotInAttachMenu', async (global, actions, payload) => {
|
||||
addActionHandler('toggleAttachBot', async (global, actions, payload) => {
|
||||
const { botId, isEnabled } = payload;
|
||||
|
||||
const bot = selectUser(global, botId);
|
||||
|
||||
if (!bot) return;
|
||||
|
||||
await toggleBotInAttachMenu(bot, isEnabled);
|
||||
await toggleAttachBot(bot, isEnabled);
|
||||
});
|
||||
|
||||
async function toggleBotInAttachMenu(bot: ApiUser, isEnabled: boolean) {
|
||||
await callApi('toggleBotInAttachMenu', { bot, isEnabled });
|
||||
await loadAttachMenuBots();
|
||||
async function toggleAttachBot(bot: ApiUser, isEnabled: boolean) {
|
||||
await callApi('toggleAttachBot', { bot, isEnabled });
|
||||
await loadAttachBots();
|
||||
}
|
||||
|
||||
async function loadAttachMenuBots(hash?: string) {
|
||||
const result = await callApi('loadAttachMenuBots', { hash });
|
||||
async function loadAttachBots(hash?: string) {
|
||||
const result = await callApi('loadAttachBots', { hash });
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
@ -593,7 +577,7 @@ async function loadAttachMenuBots(hash?: string) {
|
||||
});
|
||||
}
|
||||
|
||||
addActionHandler('callAttachMenuBot', (global, actions, payload) => {
|
||||
addActionHandler('callAttachBot', (global, actions, payload) => {
|
||||
const {
|
||||
chatId, botId, isFromBotMenu, url, startParam,
|
||||
} = payload;
|
||||
@ -601,14 +585,17 @@ addActionHandler('callAttachMenuBot', (global, actions, payload) => {
|
||||
if (!isFromBotMenu && !bots[botId]) {
|
||||
return {
|
||||
...global,
|
||||
botAttachRequest: {
|
||||
requestedAttachBotInstall: {
|
||||
botId,
|
||||
chatId,
|
||||
startParam,
|
||||
onConfirm: {
|
||||
action: 'callAttachBot',
|
||||
payload: { chatId, botId, startParam },
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
const theme = extractCurrentThemeParams();
|
||||
actions.openChat({ id: chatId });
|
||||
actions.requestWebView({
|
||||
url,
|
||||
peerId: chatId,
|
||||
@ -622,29 +609,67 @@ addActionHandler('callAttachMenuBot', (global, actions, payload) => {
|
||||
return undefined;
|
||||
});
|
||||
|
||||
addActionHandler('confirmBotAttachRequest', async (global, actions) => {
|
||||
const { botAttachRequest } = global;
|
||||
if (!botAttachRequest) return;
|
||||
addActionHandler('confirmAttachBotInstall', async (global) => {
|
||||
const { requestedAttachBotInstall } = global;
|
||||
|
||||
const { botId, chatId, startParam } = botAttachRequest;
|
||||
const { botId, onConfirm } = requestedAttachBotInstall!;
|
||||
|
||||
setGlobal({
|
||||
...global,
|
||||
botAttachRequest: undefined,
|
||||
requestedAttachBotInstall: undefined,
|
||||
});
|
||||
|
||||
const bot = selectUser(global, botId);
|
||||
if (!bot) return;
|
||||
|
||||
await toggleBotInAttachMenu(bot, true);
|
||||
|
||||
actions.callAttachMenuBot({ chatId, botId, startParam });
|
||||
await toggleAttachBot(bot, true);
|
||||
if (onConfirm) {
|
||||
const { action, payload } = onConfirm;
|
||||
getActions()[action](payload);
|
||||
}
|
||||
});
|
||||
|
||||
addActionHandler('closeBotAttachRequestModal', (global) => {
|
||||
addActionHandler('cancelAttachBotInstall', (global) => {
|
||||
return {
|
||||
...global,
|
||||
botAttachRequest: undefined,
|
||||
requestedAttachBotInstall: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
addActionHandler('requestAttachBotInChat', (global, actions, payload) => {
|
||||
const { botId, filter, startParam } = payload;
|
||||
const currentChatId = selectCurrentMessageList(global)?.chatId;
|
||||
|
||||
const { attachMenu: { bots } } = global;
|
||||
const bot = bots[botId];
|
||||
if (!bot) return;
|
||||
const supportedFilters = bot.peerTypes.filter((type): type is ApiChatType => (
|
||||
type !== 'self' && filter.includes(type)
|
||||
));
|
||||
|
||||
if (!supportedFilters.length) {
|
||||
actions.callAttachBot({
|
||||
chatId: currentChatId || botId,
|
||||
botId,
|
||||
startParam,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
setGlobal({
|
||||
...global,
|
||||
requestedAttachBotInChat: {
|
||||
botId,
|
||||
filter: supportedFilters,
|
||||
startParam,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
addActionHandler('cancelAttachBotInChat', (global) => {
|
||||
return {
|
||||
...global,
|
||||
requestedAttachBotInChat: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
@ -7,7 +7,7 @@ import type {
|
||||
} from '../../../api/types';
|
||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||
import { NewChatMembersProgress, ChatCreationProgress, ManagementProgress } from '../../../types';
|
||||
import type { GlobalActions } from '../../types';
|
||||
import type { GlobalActions, GlobalState } from '../../types';
|
||||
|
||||
import {
|
||||
ARCHIVED_FOLDER_ID,
|
||||
@ -35,9 +35,14 @@ import {
|
||||
import { buildCollectionByKey, omit } from '../../../util/iteratees';
|
||||
import { debounce, pause, throttle } from '../../../util/schedulers';
|
||||
import {
|
||||
isChatSummaryOnly, isChatArchived, isChatBasicGroup, isUserBot, isChatChannel, isChatSuperGroup,
|
||||
isChatSummaryOnly,
|
||||
isChatArchived,
|
||||
isChatBasicGroup,
|
||||
isChatChannel,
|
||||
isChatSuperGroup,
|
||||
isUserBot,
|
||||
} from '../../helpers';
|
||||
import { processDeepLink } from '../../../util/deeplink';
|
||||
import { formatShareText, parseChooseParameter, processDeepLink } from '../../../util/deeplink';
|
||||
import { updateGroupCall } from '../../reducers/calls';
|
||||
import { selectGroupCall } from '../../selectors/calls';
|
||||
import { getOrderedIds } from '../../../util/folderManager';
|
||||
@ -586,10 +591,21 @@ addActionHandler('openChatByPhoneNumber', async (global, actions, payload) => {
|
||||
|
||||
addActionHandler('openTelegramLink', (global, actions, payload) => {
|
||||
const { url } = payload!;
|
||||
const {
|
||||
openChatByPhoneNumber,
|
||||
openChatByInvite,
|
||||
openStickerSet,
|
||||
openChatWithDraft,
|
||||
joinVoiceChatByLink,
|
||||
showNotification,
|
||||
focusMessage,
|
||||
openInvoice,
|
||||
processAttachBotParameters,
|
||||
openChatByUsername: openChatByUsernameAction,
|
||||
} = actions;
|
||||
|
||||
const tgLinkMatch = url.match(RE_TG_LINK);
|
||||
if (tgLinkMatch) {
|
||||
processDeepLink(tgLinkMatch[0]);
|
||||
if (url.match(RE_TG_LINK)) {
|
||||
processDeepLink(url);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -611,9 +627,10 @@ addActionHandler('openTelegramLink', (global, actions, payload) => {
|
||||
}
|
||||
|
||||
const startAttach = params.hasOwnProperty('startattach') && !params.startattach ? true : params.startattach;
|
||||
const choose = parseChooseParameter(params.choose);
|
||||
|
||||
if (part1.match(/^\+([0-9]+)(\?|$)/)) {
|
||||
actions.openChatByPhoneNumber({
|
||||
openChatByPhoneNumber({
|
||||
phoneNumber: part1.substr(1, part1.length - 1),
|
||||
startAttach,
|
||||
attach: params.attach,
|
||||
@ -626,12 +643,12 @@ addActionHandler('openTelegramLink', (global, actions, payload) => {
|
||||
}
|
||||
|
||||
if (hash) {
|
||||
actions.openChatByInvite({ hash });
|
||||
openChatByInvite({ hash });
|
||||
return;
|
||||
}
|
||||
|
||||
if (part1 === 'addstickers' || part1 === 'addemoji') {
|
||||
actions.openStickerSet({
|
||||
openStickerSet({
|
||||
stickerSetInfo: {
|
||||
shortName: part2,
|
||||
},
|
||||
@ -643,8 +660,11 @@ addActionHandler('openTelegramLink', (global, actions, payload) => {
|
||||
const messageId = part3 ? Number(part3) : undefined;
|
||||
const commentId = params.comment ? Number(params.comment) : undefined;
|
||||
|
||||
if (params.hasOwnProperty('voicechat') || params.hasOwnProperty('livestream')) {
|
||||
actions.joinVoiceChatByLink({
|
||||
if (part1 === 'share') {
|
||||
const text = formatShareText(params.url, params.text);
|
||||
openChatWithDraft({ text });
|
||||
} else if (params.hasOwnProperty('voicechat') || params.hasOwnProperty('livestream')) {
|
||||
joinVoiceChatByLink({
|
||||
username: part1,
|
||||
inviteHash: params.voicechat || params.livestream,
|
||||
});
|
||||
@ -652,24 +672,30 @@ addActionHandler('openTelegramLink', (global, actions, payload) => {
|
||||
const chatId = `-${chatOrChannelPostId}`;
|
||||
const chat = selectChat(global, chatId);
|
||||
if (!chat) {
|
||||
actions.showNotification({ message: 'Chat does not exist' });
|
||||
showNotification({ message: 'Chat does not exist' });
|
||||
return;
|
||||
}
|
||||
|
||||
actions.focusMessage({
|
||||
focusMessage({
|
||||
chatId,
|
||||
messageId,
|
||||
});
|
||||
} else if (part1.startsWith('$')) {
|
||||
actions.openInvoice({
|
||||
openInvoice({
|
||||
slug: part1.substring(1),
|
||||
});
|
||||
} else if (part1 === 'invoice') {
|
||||
actions.openInvoice({
|
||||
openInvoice({
|
||||
slug: part2,
|
||||
});
|
||||
} else if (startAttach && choose) {
|
||||
processAttachBotParameters({
|
||||
username: part1,
|
||||
filter: choose,
|
||||
...(typeof startAttach === 'string' && { startParam: startAttach }),
|
||||
});
|
||||
} else {
|
||||
actions.openChatByUsername({
|
||||
openChatByUsernameAction({
|
||||
username: part1,
|
||||
messageId: messageId || Number(chatOrChannelPostId),
|
||||
commentId,
|
||||
@ -1002,10 +1028,10 @@ addActionHandler('setActiveChatFolder', (global, actions, payload) => {
|
||||
};
|
||||
});
|
||||
|
||||
addActionHandler('resetOpenChatWithText', (global) => {
|
||||
addActionHandler('resetOpenChatWithDraft', (global) => {
|
||||
return {
|
||||
...global,
|
||||
openChatWithText: undefined,
|
||||
requestedDraft: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
@ -1115,6 +1141,38 @@ addActionHandler('toggleJoinRequest', async (global, actions, payload) => {
|
||||
await callApi('toggleJoinRequest', chat, isEnabled);
|
||||
});
|
||||
|
||||
addActionHandler('processAttachBotParameters', async (global, actions, payload) => {
|
||||
const { username, filter, startParam } = payload;
|
||||
const bot = await getAttachBotOrNotify(global, username);
|
||||
if (!bot) return;
|
||||
|
||||
global = getGlobal();
|
||||
const { attachMenu: { bots } } = global;
|
||||
if (!bots[bot.id]) {
|
||||
setGlobal({
|
||||
...global,
|
||||
requestedAttachBotInstall: {
|
||||
botId: bot.id,
|
||||
onConfirm: {
|
||||
action: 'requestAttachBotInChat',
|
||||
payload: {
|
||||
botId: bot.id,
|
||||
filter,
|
||||
startParam,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
getActions().requestAttachBotInChat({
|
||||
botId: bot.id,
|
||||
filter,
|
||||
startParam,
|
||||
});
|
||||
});
|
||||
|
||||
async function loadChats(
|
||||
listType: 'active' | 'archived', offsetId?: string, offsetDate?: number, shouldReplace = false,
|
||||
) {
|
||||
@ -1504,6 +1562,22 @@ export async function fetchChatByPhoneNumber(phoneNumber: string) {
|
||||
return chat;
|
||||
}
|
||||
|
||||
async function getAttachBotOrNotify(global: GlobalState, username: string) {
|
||||
const chat = await fetchChatByUsername(username);
|
||||
if (!chat) return undefined;
|
||||
|
||||
const user = selectUser(global, chat.id);
|
||||
if (!user) return undefined;
|
||||
|
||||
const isBot = isUserBot(user);
|
||||
if (!isBot || !user.isAttachBot) {
|
||||
getActions().showNotification({ message: langProvider.getTranslation('WebApp.AddToAttachmentUnavailableError') });
|
||||
|
||||
return undefined;
|
||||
}
|
||||
return user;
|
||||
}
|
||||
|
||||
async function openChatByUsername(
|
||||
actions: GlobalActions,
|
||||
username: string,
|
||||
@ -1512,29 +1586,16 @@ async function openChatByUsername(
|
||||
startAttach?: string | boolean,
|
||||
attach?: string,
|
||||
) {
|
||||
let global = getGlobal();
|
||||
const global = getGlobal();
|
||||
const currentChat = selectCurrentChat(global);
|
||||
|
||||
// Attach in the current chat
|
||||
if (startAttach && !attach) {
|
||||
const chat = await fetchChatByUsername(username);
|
||||
if (!chat) return;
|
||||
const user = await getAttachBotOrNotify(global, username);
|
||||
|
||||
global = getGlobal();
|
||||
if (!currentChat || !user) return;
|
||||
|
||||
const user = selectUser(global, chat.id);
|
||||
if (!user) return;
|
||||
|
||||
const isBot = isUserBot(user);
|
||||
if (!isBot || !user.isAttachMenuBot) {
|
||||
actions.showNotification({ message: langProvider.getTranslation('WebApp.AddToAttachmentUnavailableError') });
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!currentChat) return;
|
||||
|
||||
actions.callAttachMenuBot({
|
||||
actions.callAttachBot({
|
||||
botId: user.id,
|
||||
chatId: currentChat.id,
|
||||
...(typeof startAttach === 'string' && { startParam: startAttach }),
|
||||
@ -1582,12 +1643,12 @@ async function openAttachMenuFromLink(
|
||||
const botChat = await fetchChatByUsername(attach);
|
||||
if (!botChat) return;
|
||||
const botUser = selectUser(getGlobal(), botChat.id);
|
||||
if (!botUser || !botUser.isAttachMenuBot) {
|
||||
if (!botUser || !botUser.isAttachBot) {
|
||||
actions.showNotification({ message: langProvider.getTranslation('WebApp.AddToAttachmentUnavailableError') });
|
||||
return;
|
||||
}
|
||||
|
||||
actions.callAttachMenuBot({
|
||||
actions.callAttachBot({
|
||||
botId: botUser.id,
|
||||
chatId,
|
||||
...(typeof startAttach === 'string' && { startParam: startAttach }),
|
||||
|
||||
@ -70,14 +70,16 @@ addActionHandler('openChatWithInfo', (global, actions, payload) => {
|
||||
actions.openChat(payload);
|
||||
});
|
||||
|
||||
addActionHandler('openChatWithText', (global, actions, payload) => {
|
||||
addActionHandler('openChatWithDraft', (global, actions, payload) => {
|
||||
const { chatId, text } = payload;
|
||||
|
||||
actions.openChat({ id: chatId });
|
||||
if (chatId) {
|
||||
actions.openChat({ id: chatId });
|
||||
}
|
||||
|
||||
return {
|
||||
...global,
|
||||
openChatWithText: {
|
||||
requestedDraft: {
|
||||
chatId,
|
||||
text,
|
||||
},
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ApiAttachMenuPeerType, ApiChat } from '../../api/types';
|
||||
import type { ApiChatType, ApiChat } from '../../api/types';
|
||||
import { MAIN_THREAD_ID } from '../../api/types';
|
||||
import type { GlobalState } from '../types';
|
||||
|
||||
@ -74,25 +74,25 @@ export function selectIsTrustedBot(global: GlobalState, botId: string) {
|
||||
return bot && (bot.isVerified || global.trustedBotIds.includes(botId));
|
||||
}
|
||||
|
||||
export function selectAttachMenuPeerType(global: GlobalState, chatId: string) : ApiAttachMenuPeerType | undefined {
|
||||
export function selectChatType(global: GlobalState, chatId: string) : ApiChatType | undefined {
|
||||
const chat = selectChat(global, chatId);
|
||||
if (!chat) return undefined;
|
||||
|
||||
const bot = selectBot(global, chatId);
|
||||
if (bot) {
|
||||
return 'bot';
|
||||
return 'bots';
|
||||
}
|
||||
|
||||
const user = selectChatUser(global, chat);
|
||||
if (user) {
|
||||
return 'private';
|
||||
return 'users';
|
||||
}
|
||||
|
||||
if (isChatChannel(chat)) {
|
||||
return 'channel';
|
||||
return 'channels';
|
||||
}
|
||||
|
||||
return 'chat';
|
||||
return 'chats';
|
||||
}
|
||||
|
||||
export function selectIsChatBotNotStarted(global: GlobalState, chatId: string) {
|
||||
@ -194,8 +194,18 @@ export function selectSendAs(global: GlobalState, chatId: string) {
|
||||
}
|
||||
|
||||
export function selectRequestedText(global: GlobalState, chatId: string) {
|
||||
if (global.openChatWithText?.chatId === chatId) {
|
||||
return global.openChatWithText.text;
|
||||
if (global.requestedDraft?.chatId === chatId) {
|
||||
return global.requestedDraft.text;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function filterChatIdsByType(global: GlobalState, chatIds: string[], filter: readonly ApiChatType[]) {
|
||||
return chatIds.filter((id) => {
|
||||
const type = selectChatType(global, id);
|
||||
if (!type) {
|
||||
return false;
|
||||
}
|
||||
return filter.includes(type);
|
||||
});
|
||||
}
|
||||
|
||||
@ -673,8 +673,8 @@ export function selectIsPollResultsOpen(global: GlobalState) {
|
||||
}
|
||||
|
||||
export function selectIsForwardModalOpen(global: GlobalState) {
|
||||
const { forwardMessages, switchBotInline } = global;
|
||||
return Boolean(switchBotInline || forwardMessages.isModalShown);
|
||||
const { forwardMessages } = global;
|
||||
return Boolean(forwardMessages.isModalShown);
|
||||
}
|
||||
|
||||
export function selectCommonBoxChatId(global: GlobalState, messageId: number) {
|
||||
|
||||
@ -35,7 +35,7 @@ import type {
|
||||
ApiPhoto,
|
||||
ApiKeyboardButton,
|
||||
ApiThemeParameters,
|
||||
ApiAttachMenuBot,
|
||||
ApiAttachBot,
|
||||
ApiPhoneCall,
|
||||
ApiWebSession,
|
||||
ApiPremiumPromo,
|
||||
@ -43,6 +43,7 @@ import type {
|
||||
ApiInputInvoice,
|
||||
ApiInvoice,
|
||||
ApiStickerSetInfo,
|
||||
ApiChatType,
|
||||
} from '../api/types';
|
||||
import type {
|
||||
FocusDirection,
|
||||
@ -585,13 +586,8 @@ export type GlobalState = {
|
||||
messageId: number;
|
||||
};
|
||||
|
||||
switchBotInline?: {
|
||||
query: string;
|
||||
botUsername: string;
|
||||
};
|
||||
|
||||
openChatWithText?: {
|
||||
chatId: string;
|
||||
requestedDraft?: {
|
||||
chatId?: string;
|
||||
text: string;
|
||||
};
|
||||
|
||||
@ -614,18 +610,25 @@ export type GlobalState = {
|
||||
type: 'game' | 'webApp';
|
||||
onConfirm?: {
|
||||
action: keyof GlobalActions;
|
||||
payload: any; // TODO add TS support
|
||||
payload: any; // TODO Add TS support
|
||||
};
|
||||
};
|
||||
botAttachRequest?: {
|
||||
requestedAttachBotInstall?: {
|
||||
botId: string;
|
||||
chatId: string;
|
||||
onConfirm?: {
|
||||
action: keyof GlobalActions;
|
||||
payload: any; // TODO Add TS support
|
||||
};
|
||||
};
|
||||
requestedAttachBotInChat?: {
|
||||
botId: string;
|
||||
filter: ApiChatType[];
|
||||
startParam?: string;
|
||||
};
|
||||
|
||||
attachMenu: {
|
||||
hash?: string;
|
||||
bots: Record<string, ApiAttachMenuBot>;
|
||||
bots: Record<string, ApiAttachBot>;
|
||||
};
|
||||
|
||||
confetti?: {
|
||||
@ -722,11 +725,11 @@ export interface ActionPayloads {
|
||||
shouldReplaceHistory?: boolean;
|
||||
};
|
||||
|
||||
openChatWithText: {
|
||||
chatId: string;
|
||||
openChatWithDraft: {
|
||||
chatId?: string;
|
||||
text: string;
|
||||
};
|
||||
resetOpenChatWithText: never;
|
||||
resetOpenChatWithDraft: never;
|
||||
|
||||
toggleJoinToSend: {
|
||||
chatId: string;
|
||||
@ -956,8 +959,6 @@ export interface ActionPayloads {
|
||||
isSamePeer?: boolean;
|
||||
};
|
||||
|
||||
resetSwitchBotInline: never;
|
||||
|
||||
openGame: {
|
||||
url: string;
|
||||
chatId: string;
|
||||
@ -998,8 +999,20 @@ export interface ActionPayloads {
|
||||
botId: string;
|
||||
};
|
||||
|
||||
closeBotAttachRequestModal: never;
|
||||
confirmBotAttachRequest: never;
|
||||
cancelAttachBotInstall: never;
|
||||
confirmAttachBotInstall: never;
|
||||
|
||||
processAttachBotParameters: {
|
||||
username: string;
|
||||
filter: ApiChatType[];
|
||||
startParam?: string;
|
||||
};
|
||||
requestAttachBotInChat: {
|
||||
botId: string;
|
||||
filter: ApiChatType[];
|
||||
startParam?: string;
|
||||
};
|
||||
cancelAttachBotInChat: never;
|
||||
|
||||
sendWebViewData: {
|
||||
bot: ApiUser;
|
||||
@ -1007,16 +1020,16 @@ export interface ActionPayloads {
|
||||
buttonText: string;
|
||||
};
|
||||
|
||||
loadAttachMenuBots: {
|
||||
loadAttachBots: {
|
||||
hash?: string;
|
||||
};
|
||||
|
||||
toggleBotInAttachMenu: {
|
||||
toggleAttachBot: {
|
||||
botId: string;
|
||||
isEnabled: boolean;
|
||||
};
|
||||
|
||||
callAttachMenuBot: {
|
||||
callAttachBot: {
|
||||
chatId: string;
|
||||
botId: string;
|
||||
isFromBotMenu?: boolean;
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
import { getActions } from '../global';
|
||||
|
||||
import type { ApiChatType } from '../api/types';
|
||||
|
||||
import { API_CHAT_TYPES } from '../config';
|
||||
import { IS_SAFARI } from './environment';
|
||||
|
||||
type DeepLinkMethod = 'resolve' | 'login' | 'passport' | 'settings' | 'join' | 'addstickers' | 'addemoji' |
|
||||
@ -20,14 +24,13 @@ export const processDeepLink = (url: string) => {
|
||||
focusMessage,
|
||||
joinVoiceChatByLink,
|
||||
openInvoice,
|
||||
processAttachBotParameters,
|
||||
openChatWithDraft,
|
||||
} = getActions();
|
||||
|
||||
// Safari thinks the path in tg://path links is hostname for some reason
|
||||
const method = (IS_SAFARI ? hostname : pathname).replace(/^\/\//, '') as DeepLinkMethod;
|
||||
const params: Record<string, string> = {};
|
||||
searchParams.forEach((value, key) => {
|
||||
params[key] = value;
|
||||
});
|
||||
const params = Object.fromEntries(searchParams);
|
||||
|
||||
switch (method) {
|
||||
case 'resolve': {
|
||||
@ -36,9 +39,16 @@ export const processDeepLink = (url: string) => {
|
||||
} = params;
|
||||
|
||||
const startAttach = params.hasOwnProperty('startattach') && !startattach ? true : startattach;
|
||||
const choose = parseChooseParameter(params.choose);
|
||||
|
||||
if (domain !== 'telegrampassport') {
|
||||
if (params.hasOwnProperty('voicechat') || params.hasOwnProperty('livestream')) {
|
||||
if (startAttach && choose) {
|
||||
processAttachBotParameters({
|
||||
username: domain,
|
||||
filter: choose,
|
||||
...(typeof startAttach === 'string' && { startParam: startAttach }),
|
||||
});
|
||||
} else if (params.hasOwnProperty('voicechat') || params.hasOwnProperty('livestream')) {
|
||||
joinVoiceChatByLink({
|
||||
username: domain,
|
||||
inviteHash: voicechat || livestream,
|
||||
@ -93,8 +103,10 @@ export const processDeepLink = (url: string) => {
|
||||
break;
|
||||
}
|
||||
case 'share':
|
||||
case 'msg': {
|
||||
// const { url, text } = params;
|
||||
case 'msg':
|
||||
case 'msg_url': {
|
||||
const { url: urlParam, text } = params;
|
||||
openChatWithDraft({ text: formatShareText(urlParam, text) });
|
||||
break;
|
||||
}
|
||||
case 'login': {
|
||||
@ -113,3 +125,13 @@ export const processDeepLink = (url: string) => {
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
export function parseChooseParameter(choose?: string) {
|
||||
if (!choose) return undefined;
|
||||
const types = choose.toLowerCase().split(' ');
|
||||
return types.filter((type): type is ApiChatType => API_CHAT_TYPES.includes(type as ApiChatType));
|
||||
}
|
||||
|
||||
export function formatShareText(url?: string, text?: string) {
|
||||
return [url, text].filter(Boolean).join('\n');
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user