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