Forward: Fix forward to forum topics (#6842)
Co-authored-by: Dmitry Kabanov <153344039+dmitrykabanovdev@users.noreply.github.com> Co-authored-by: Dmitry Kabanov <dmitrykabanovdev@gmail.com>
This commit is contained in:
parent
7b26965b45
commit
d7e3456550
@ -22,6 +22,13 @@ import {
|
||||
import { selectCurrentLimit } from '../../global/selectors/limits';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { unique } from '../../util/iteratees';
|
||||
import {
|
||||
areChatSelectionKeysEqual,
|
||||
buildChatSelectionKey,
|
||||
type ChatSelectionKey,
|
||||
getChatSelectionKeyHash,
|
||||
includesChatSelectionKey,
|
||||
} from '../../util/keys/chatSelectionKey';
|
||||
import sortChatIds from './helpers/sortChatIds';
|
||||
|
||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||
@ -52,7 +59,7 @@ export type OwnProps = {
|
||||
viewportFooter?: TeactNode;
|
||||
loadMore?: NoneToVoidFunction;
|
||||
onSelectRecipient: (peerId: string, threadId?: ThreadId) => void;
|
||||
onSelectedIdsChange?: (ids: string[]) => void;
|
||||
onSelectedIdsChange?: (ids: ChatSelectionKey[]) => void;
|
||||
onClose: NoneToVoidFunction;
|
||||
onCloseAnimationEnd?: NoneToVoidFunction;
|
||||
};
|
||||
@ -100,9 +107,9 @@ const RecipientPicker = ({
|
||||
const lang = useLang();
|
||||
|
||||
const [search, setSearch] = useState('');
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||
const [removingIds, setRemovingIds] = useState<string[]>([]);
|
||||
const [appearingIds, setAppearingIds] = useState<string[]>([]);
|
||||
const [selectedIds, setSelectedIds] = useState<ChatSelectionKey[]>([]);
|
||||
const [removingIds, setRemovingIds] = useState<ChatSelectionKey[]>([]);
|
||||
const [appearingIds, setAppearingIds] = useState<ChatSelectionKey[]>([]);
|
||||
const selectedIdsRef = useStateRef(selectedIds);
|
||||
const removingIdsRef = useStateRef(removingIds);
|
||||
const appearingIdsRef = useStateRef(appearingIds);
|
||||
@ -134,46 +141,46 @@ const RecipientPicker = ({
|
||||
setActiveFolderIndex(index);
|
||||
});
|
||||
|
||||
const updateSelectedIds = useLastCallback((newIds: string[], newlyAddedId?: string) => {
|
||||
const updateSelectedIds = useLastCallback((newIds: ChatSelectionKey[], newlyAddedKey?: ChatSelectionKey) => {
|
||||
setSelectedIds(newIds);
|
||||
onSelectedIdsChange?.(newIds);
|
||||
|
||||
if (newlyAddedId && selectCanAnimateInterface(getGlobal())) {
|
||||
setAppearingIds([...appearingIdsRef.current, newlyAddedId]);
|
||||
if (newlyAddedKey && selectCanAnimateInterface(getGlobal())) {
|
||||
setAppearingIds([...appearingIdsRef.current, newlyAddedKey]);
|
||||
setTimeout(() => {
|
||||
setAppearingIds(appearingIdsRef.current.filter((id) => id !== newlyAddedId));
|
||||
setAppearingIds(appearingIdsRef.current.filter((key) => !areChatSelectionKeysEqual(key, newlyAddedKey)));
|
||||
}, 200);
|
||||
}
|
||||
});
|
||||
|
||||
const handleRemoveSelected = useLastCallback((selectionId: string) => {
|
||||
if (removingIdsRef.current.includes(selectionId)) return;
|
||||
const handleRemoveSelected = useLastCallback((selectionKey: ChatSelectionKey) => {
|
||||
if (includesChatSelectionKey(removingIdsRef.current, selectionKey)) return;
|
||||
|
||||
const canAnimate = selectCanAnimateInterface(getGlobal());
|
||||
if (!canAnimate) {
|
||||
const newIds = selectedIdsRef.current.filter((id) => id !== selectionId);
|
||||
const newIds = selectedIdsRef.current.filter((key) => !areChatSelectionKeysEqual(key, selectionKey));
|
||||
setSelectedIds(newIds);
|
||||
onSelectedIdsChange?.(newIds);
|
||||
return;
|
||||
}
|
||||
|
||||
setRemovingIds([...removingIdsRef.current, selectionId]);
|
||||
setRemovingIds([...removingIdsRef.current, selectionKey]);
|
||||
|
||||
setTimeout(() => {
|
||||
setRemovingIds(removingIdsRef.current.filter((id) => id !== selectionId));
|
||||
const newIds = selectedIdsRef.current.filter((id) => id !== selectionId);
|
||||
setRemovingIds(removingIdsRef.current.filter((key) => !areChatSelectionKeysEqual(key, selectionKey)));
|
||||
const newIds = selectedIdsRef.current.filter((key) => !areChatSelectionKeysEqual(key, selectionKey));
|
||||
setSelectedIds(newIds);
|
||||
onSelectedIdsChange?.(newIds);
|
||||
}, 300);
|
||||
});
|
||||
|
||||
const handleToggleSelection = useLastCallback((peerId: string, threadId?: ThreadId) => {
|
||||
const selectionId = threadId ? `${peerId}:${threadId}` : peerId;
|
||||
const selectionKey = buildChatSelectionKey(peerId, threadId ? Number(threadId) : undefined);
|
||||
|
||||
if (selectedIds.includes(selectionId)) {
|
||||
handleRemoveSelected(selectionId);
|
||||
if (includesChatSelectionKey(selectedIds, selectionKey)) {
|
||||
handleRemoveSelected(selectionKey);
|
||||
} else {
|
||||
updateSelectedIds([...selectedIds, selectionId], selectionId);
|
||||
updateSelectedIds([...selectedIds, selectionKey], selectionKey);
|
||||
}
|
||||
});
|
||||
|
||||
@ -262,19 +269,8 @@ const RecipientPicker = ({
|
||||
|
||||
const hasSelectedChips = isMultiSelect && selectedIds.length > 0;
|
||||
|
||||
const parseSelectionId = useLastCallback((selectionId: string): { peerId: string; topicId?: number } => {
|
||||
const colonIndex = selectionId.indexOf(':');
|
||||
if (colonIndex === -1) {
|
||||
return { peerId: selectionId };
|
||||
}
|
||||
return {
|
||||
peerId: selectionId.substring(0, colonIndex),
|
||||
topicId: Number(selectionId.substring(colonIndex + 1)),
|
||||
};
|
||||
});
|
||||
|
||||
const getChipTitle = useLastCallback((selectionId: string): string | undefined => {
|
||||
const { peerId, topicId } = parseSelectionId(selectionId);
|
||||
const getChipTitle = useLastCallback((selectionKey: ChatSelectionKey): string | undefined => {
|
||||
const { peerId, topicId } = selectionKey;
|
||||
if (!topicId) return undefined;
|
||||
|
||||
const global = getGlobal();
|
||||
@ -309,15 +305,16 @@ const RecipientPicker = ({
|
||||
return (
|
||||
<div className="search-row-with-chips">
|
||||
<div className="chips-and-search-scroll no-scrollbar">
|
||||
{selectedIds.map((selectionId) => {
|
||||
const { peerId } = parseSelectionId(selectionId);
|
||||
const chipTitle = getChipTitle(selectionId);
|
||||
const isAppearing = appearingIds.includes(selectionId);
|
||||
const isRemoving = removingIds.includes(selectionId);
|
||||
{selectedIds.map((selectionKey) => {
|
||||
const { peerId } = selectionKey;
|
||||
const chipTitle = getChipTitle(selectionKey);
|
||||
const isAppearing = includesChatSelectionKey(appearingIds, selectionKey);
|
||||
const isRemoving = includesChatSelectionKey(removingIds, selectionKey);
|
||||
const keyHash = getChatSelectionKeyHash(selectionKey);
|
||||
|
||||
return (
|
||||
<div
|
||||
key={selectionId}
|
||||
key={keyHash}
|
||||
className={buildClassName(
|
||||
'picker-chip-wrapper',
|
||||
isAppearing && 'picker-chip-appear',
|
||||
@ -332,7 +329,7 @@ const RecipientPicker = ({
|
||||
canClose
|
||||
className="picker-chip"
|
||||
itemClassName="picker-chip-name"
|
||||
clickArg={selectionId}
|
||||
clickArg={selectionKey}
|
||||
onClick={handleRemoveSelected}
|
||||
/>
|
||||
</div>
|
||||
@ -356,6 +353,8 @@ const RecipientPicker = ({
|
||||
);
|
||||
}, [hasSelectedChips, selectedIds, appearingIds, removingIds]);
|
||||
|
||||
const selectedPeerIds = useMemo(() => selectedIds.map((key) => key.peerId), [selectedIds]);
|
||||
|
||||
const subheaderContent = useMemo(() => {
|
||||
const hasRecentContacts = recentContactIds.length > 0 && !search;
|
||||
const hasFolderTabs = shouldRenderFolders;
|
||||
@ -368,7 +367,7 @@ const RecipientPicker = ({
|
||||
<PickerRecentContacts
|
||||
contactIds={recentContactIds}
|
||||
currentUserId={currentUserId}
|
||||
selectedIds={isMultiSelect ? selectedIds : undefined}
|
||||
selectedIds={isMultiSelect ? selectedPeerIds : undefined}
|
||||
className={styles.recentContacts}
|
||||
onSelect={handleSelect}
|
||||
/>
|
||||
@ -389,7 +388,7 @@ const RecipientPicker = ({
|
||||
currentUserId,
|
||||
handleSelect,
|
||||
isMultiSelect,
|
||||
selectedIds,
|
||||
selectedPeerIds,
|
||||
folderTabs,
|
||||
activeFolderIndex,
|
||||
handleSwitchFolderIndex,
|
||||
|
||||
@ -22,6 +22,11 @@ import {
|
||||
} from '../../../global/selectors';
|
||||
import { selectAnimationLevel } from '../../../global/selectors/sharedState';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import {
|
||||
buildChatSelectionKey,
|
||||
type ChatSelectionKey,
|
||||
includesChatSelectionKey,
|
||||
} from '../../../util/keys/chatSelectionKey';
|
||||
import { resolveTransitionName } from '../../../util/resolveTransitionName';
|
||||
import { REM } from '../helpers/mediaDimensions';
|
||||
import renderText from '../helpers/renderText';
|
||||
@ -60,7 +65,7 @@ export type OwnProps = {
|
||||
renderSearchRow?: (props: SearchRowRenderProps) => TeactNode;
|
||||
footer?: TeactNode;
|
||||
viewportFooter?: TeactNode;
|
||||
selectedIds?: string[];
|
||||
selectedIds?: ChatSelectionKey[];
|
||||
loadMore?: NoneToVoidFunction;
|
||||
onSearchChange: (search: string) => void;
|
||||
onSelectChatOrUser: (chatOrUserId: string, threadId?: ThreadId) => void;
|
||||
@ -237,10 +242,10 @@ const ChatOrUserPicker = ({
|
||||
const isForum = chat?.isForum;
|
||||
|
||||
const isSelf = peer && !isApiPeerChat(peer) ? peer.isSelf : undefined;
|
||||
const isSelected = selectedIds?.includes(id);
|
||||
const isSelected = selectedIds && includesChatSelectionKey(selectedIds, buildChatSelectionKey(id));
|
||||
|
||||
const selectedTopicsCount = isForum && selectedIds
|
||||
? selectedIds.filter((selId) => selId.startsWith(`${id}:`)).length
|
||||
? selectedIds.filter((key) => key.peerId === id && key.topicId !== undefined).length
|
||||
: 0;
|
||||
const hasSelectedTopics = selectedTopicsCount > 0;
|
||||
|
||||
@ -342,8 +347,8 @@ const ChatOrUserPicker = ({
|
||||
onKeyDown={handleTopicKeyDown}
|
||||
>
|
||||
{topicIds.map((topicId, i) => {
|
||||
const selectionId = `${forumId}:${topicId}`;
|
||||
const isTopicSelected = selectedIds?.includes(selectionId);
|
||||
const chatSelectionKey = buildChatSelectionKey(forumId!, topicId);
|
||||
const isTopicSelected = selectedIds && includesChatSelectionKey(selectedIds, chatSelectionKey);
|
||||
|
||||
const topicCheckboxElement = isMultiSelect ? (
|
||||
<div className={buildClassName('picker-checkbox', isTopicSelected && 'selected')}>
|
||||
|
||||
@ -4,7 +4,8 @@ import {
|
||||
} from '../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||
|
||||
import type { ThreadId } from '../../types';
|
||||
import type { ForwardTarget, ThreadId } from '../../types';
|
||||
import type { ChatSelectionKey } from '../../util/keys/chatSelectionKey';
|
||||
|
||||
import { getChatTitle, getUserFirstOrLastName } from '../../global/helpers';
|
||||
import {
|
||||
@ -78,7 +79,7 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const renderingIsStory = usePreviousDeprecated(isStory, true);
|
||||
const [isShown, markIsShown, unmarkIsShown] = useFlag();
|
||||
const [selectedIds, setSelectedIds] = useState<string[]>([]);
|
||||
const [selectedIds, setSelectedIds] = useState<ChatSelectionKey[]>([]);
|
||||
const [caption, setCaption] = useState('');
|
||||
const [isPaymentConfirmOpen, openPaymentConfirm, closePaymentConfirm] = useFlag();
|
||||
const [shouldAutoApprove, setShouldAutoApprove] = useState(shouldPaidMessageAutoApprove);
|
||||
@ -90,20 +91,20 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
||||
if (!selectedIds.length) return { paidChatsCount: 0, totalStars: 0, totalMessages: 0 };
|
||||
|
||||
const global = getGlobal();
|
||||
let paidChatsCount = 0;
|
||||
const paidChatIds = new Set<string>();
|
||||
let totalStars = 0;
|
||||
const hasCaption = caption.trim().length > 0;
|
||||
const totalMessages = messageCount + (hasCaption ? 1 : 0);
|
||||
|
||||
for (const chatId of selectedIds) {
|
||||
for (const { peerId: chatId } of selectedIds) {
|
||||
const paidStars = selectPeerPaidMessagesStars(global, chatId);
|
||||
if (paidStars) {
|
||||
paidChatsCount++;
|
||||
paidChatIds.add(chatId);
|
||||
totalStars += paidStars * totalMessages;
|
||||
}
|
||||
}
|
||||
|
||||
return { paidChatsCount, totalStars, totalMessages };
|
||||
return { paidChatsCount: paidChatIds.size, totalStars, totalMessages };
|
||||
}, [selectedIds, messageCount, caption]);
|
||||
|
||||
const canCopyLink = useMemo(() => {
|
||||
@ -179,7 +180,7 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
||||
exitForwardMode();
|
||||
}, [exitForwardMode]);
|
||||
|
||||
const handleSelectedIdsChange = useLastCallback((ids: string[]) => {
|
||||
const handleSelectedIdsChange = useLastCallback((ids: ChatSelectionKey[]) => {
|
||||
setSelectedIds(ids);
|
||||
});
|
||||
|
||||
@ -196,7 +197,8 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
||||
if (!selectedIds.length) return;
|
||||
|
||||
if (selectedIds.length === 1) {
|
||||
setForwardChatOrTopic({ chatId: selectedIds[0] });
|
||||
const { peerId: chatId, topicId } = selectedIds[0];
|
||||
setForwardChatOrTopic({ chatId, topicId });
|
||||
return;
|
||||
}
|
||||
|
||||
@ -221,7 +223,11 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
|
||||
const executeForward = useLastCallback(() => {
|
||||
forwardToMultipleChats({ toChatIds: selectedIds, comment: caption || undefined });
|
||||
const targets: ForwardTarget[] = selectedIds.map(({ peerId, topicId }) => ({
|
||||
chatId: peerId,
|
||||
topicId,
|
||||
}));
|
||||
forwardToMultipleChats({ targets, comment: caption || undefined });
|
||||
|
||||
showNotification({
|
||||
message: lang('FwdMessagesToChats', { count: selectedIds.length }, { pluralValue: selectedIds.length }),
|
||||
|
||||
@ -2620,6 +2620,7 @@ interface ForwardToChatOptions {
|
||||
global: GlobalState;
|
||||
fromChat: ApiChat;
|
||||
toChat: ApiChat;
|
||||
toThreadId?: ThreadId;
|
||||
realMessages: ApiMessage[];
|
||||
serviceMessages: ApiMessage[];
|
||||
comment?: string;
|
||||
@ -2633,6 +2634,7 @@ function forwardMessagesToChat({
|
||||
global,
|
||||
fromChat,
|
||||
toChat,
|
||||
toThreadId = MAIN_THREAD_ID,
|
||||
realMessages,
|
||||
serviceMessages,
|
||||
comment,
|
||||
@ -2642,12 +2644,21 @@ function forwardMessagesToChat({
|
||||
isCurrentUserPremium,
|
||||
}: ForwardToChatOptions) {
|
||||
const sendAs = selectSendAs(global, toChat.id);
|
||||
const lastMessageId = selectChatLastMessageId(global, toChat.id);
|
||||
const threadInfo = toThreadId !== MAIN_THREAD_ID ? selectThreadInfo(global, toChat.id, toThreadId) : undefined;
|
||||
const lastMessageId = toThreadId === MAIN_THREAD_ID
|
||||
? selectChatLastMessageId(global, toChat.id)
|
||||
: threadInfo?.lastMessageId;
|
||||
const messagePriceInStars = selectPeerPaidMessagesStars(global, toChat.id);
|
||||
const targetMessageList = {
|
||||
chatId: toChat.id,
|
||||
threadId: toThreadId,
|
||||
type: 'thread',
|
||||
} as const;
|
||||
|
||||
if (comment) {
|
||||
sendMessage(global, {
|
||||
chat: toChat,
|
||||
messageList: targetMessageList,
|
||||
text: comment,
|
||||
sendAs,
|
||||
lastMessageId,
|
||||
@ -2664,7 +2675,7 @@ function forwardMessagesToChat({
|
||||
const forwardParams: ForwardMessagesParams = {
|
||||
fromChat,
|
||||
toChat,
|
||||
toThreadId: MAIN_THREAD_ID,
|
||||
toThreadId,
|
||||
messages: slice,
|
||||
isSilent: true,
|
||||
sendAs,
|
||||
@ -2687,6 +2698,7 @@ function forwardMessagesToChat({
|
||||
|
||||
sendMessage(global, {
|
||||
chat: toChat,
|
||||
messageList: targetMessageList,
|
||||
text,
|
||||
entities,
|
||||
sticker,
|
||||
@ -2699,7 +2711,7 @@ function forwardMessagesToChat({
|
||||
}
|
||||
|
||||
addActionHandler('forwardToMultipleChats', (global, actions, payload): ActionReturnType => {
|
||||
const { toChatIds, comment, tabId = getCurrentTabId() } = payload;
|
||||
const { targets, comment, tabId = getCurrentTabId() } = payload;
|
||||
|
||||
const {
|
||||
fromChatId, messageIds, withMyScore, noAuthors, noCaptions,
|
||||
@ -2725,14 +2737,15 @@ addActionHandler('forwardToMultipleChats', (global, actions, payload): ActionRet
|
||||
return;
|
||||
}
|
||||
|
||||
for (const toChatId of toChatIds) {
|
||||
const toChat = selectChat(global, toChatId);
|
||||
for (const { chatId, topicId } of targets) {
|
||||
const toChat = selectChat(global, chatId);
|
||||
if (!toChat) continue;
|
||||
|
||||
forwardMessagesToChat({
|
||||
global,
|
||||
fromChat,
|
||||
toChat,
|
||||
toThreadId: topicId || MAIN_THREAD_ID,
|
||||
realMessages: forwardableRealMessages,
|
||||
serviceMessages,
|
||||
comment,
|
||||
|
||||
@ -78,6 +78,7 @@ import type {
|
||||
CallSound,
|
||||
ChatListType,
|
||||
ConfettiParams,
|
||||
ForwardTarget,
|
||||
GiftProfileFilterOptions,
|
||||
GlobalSearchContent,
|
||||
IAnchorPosition,
|
||||
@ -2024,7 +2025,7 @@ export interface ActionPayloads {
|
||||
exitForwardMode: WithTabId | undefined;
|
||||
changeRecipient: WithTabId | undefined;
|
||||
forwardToMultipleChats: {
|
||||
toChatIds: string[];
|
||||
targets: ForwardTarget[];
|
||||
comment?: string;
|
||||
} & WithTabId;
|
||||
forwardToSavedMessages: {
|
||||
|
||||
@ -105,6 +105,11 @@ export interface IDocumentGroup {
|
||||
|
||||
export type ThreadId = string | number;
|
||||
|
||||
export type ForwardTarget = {
|
||||
chatId: string;
|
||||
topicId?: number;
|
||||
};
|
||||
|
||||
export type ThemeKey = 'light' | 'dark';
|
||||
export type AnimationLevel = 0 | 1 | 2;
|
||||
export type FoldersPosition = 'top' | 'left';
|
||||
|
||||
24
src/util/keys/chatSelectionKey.ts
Normal file
24
src/util/keys/chatSelectionKey.ts
Normal file
@ -0,0 +1,24 @@
|
||||
export type ChatSelectionKey = {
|
||||
peerId: string;
|
||||
topicId?: number;
|
||||
};
|
||||
|
||||
export function buildChatSelectionKey(peerId: string, topicId?: number): ChatSelectionKey {
|
||||
return { peerId, topicId };
|
||||
}
|
||||
|
||||
export function areChatSelectionKeysEqual(a: ChatSelectionKey, b: ChatSelectionKey): boolean {
|
||||
return a.peerId === b.peerId && a.topicId === b.topicId;
|
||||
}
|
||||
|
||||
export function includesChatSelectionKey(arr: ChatSelectionKey[], key: ChatSelectionKey): boolean {
|
||||
return arr.some((k) => areChatSelectionKeysEqual(k, key));
|
||||
}
|
||||
|
||||
export function findChatSelectionKeyIndex(arr: ChatSelectionKey[], key: ChatSelectionKey): number {
|
||||
return arr.findIndex((k) => areChatSelectionKeysEqual(k, key));
|
||||
}
|
||||
|
||||
export function getChatSelectionKeyHash(key: ChatSelectionKey): string {
|
||||
return key.topicId !== undefined ? `${key.peerId}:${key.topicId}` : key.peerId;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user