Forum: Fix empty topic preview after reload (#4981)
This commit is contained in:
parent
73d9fe9756
commit
ba762fd0ad
@ -1,4 +1,3 @@
|
|||||||
import type { ThreadId } from '../../types';
|
|
||||||
import type { ApiBotCommand } from './bots';
|
import type { ApiBotCommand } from './bots';
|
||||||
import type {
|
import type {
|
||||||
ApiChatReactions, ApiFormattedText, ApiPhoto, ApiStickerSet,
|
ApiChatReactions, ApiFormattedText, ApiPhoto, ApiStickerSet,
|
||||||
@ -48,10 +47,6 @@ export interface ApiChat {
|
|||||||
emojiStatus?: ApiEmojiStatus;
|
emojiStatus?: ApiEmojiStatus;
|
||||||
isForum?: boolean;
|
isForum?: boolean;
|
||||||
isForumAsMessages?: true;
|
isForumAsMessages?: true;
|
||||||
topics?: Record<ThreadId, ApiTopic>;
|
|
||||||
listedTopicIds?: number[];
|
|
||||||
topicsCount?: number;
|
|
||||||
orderedPinnedTopicIds?: number[];
|
|
||||||
boostLevel?: number;
|
boostLevel?: number;
|
||||||
|
|
||||||
// Calls
|
// Calls
|
||||||
|
|||||||
@ -8,7 +8,7 @@ import React, {
|
|||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
import type { ApiChat } from '../../api/types';
|
import type { ApiChat, ApiTopic } from '../../api/types';
|
||||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
import { getOrderedTopics } from '../../global/helpers';
|
import { getOrderedTopics } from '../../global/helpers';
|
||||||
@ -26,6 +26,7 @@ import styles from './ChatForumLastMessage.module.scss';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
|
topics?: Record<number, ApiTopic>;
|
||||||
renderLastMessage: () => React.ReactNode;
|
renderLastMessage: () => React.ReactNode;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
};
|
};
|
||||||
@ -35,6 +36,7 @@ const MAX_TOPICS = 3;
|
|||||||
|
|
||||||
const ChatForumLastMessage: FC<OwnProps> = ({
|
const ChatForumLastMessage: FC<OwnProps> = ({
|
||||||
chat,
|
chat,
|
||||||
|
topics,
|
||||||
renderLastMessage,
|
renderLastMessage,
|
||||||
observeIntersection,
|
observeIntersection,
|
||||||
}) => {
|
}) => {
|
||||||
@ -48,12 +50,12 @@ const ChatForumLastMessage: FC<OwnProps> = ({
|
|||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
|
|
||||||
const [lastActiveTopic, ...otherTopics] = useMemo(() => {
|
const [lastActiveTopic, ...otherTopics] = useMemo(() => {
|
||||||
if (!chat.topics) {
|
if (!topics) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return getOrderedTopics(Object.values(chat.topics), undefined, true).slice(0, MAX_TOPICS);
|
return getOrderedTopics(Object.values(topics), undefined, true).slice(0, MAX_TOPICS);
|
||||||
}, [chat.topics]);
|
}, [topics]);
|
||||||
|
|
||||||
const [isReversedCorner, setIsReversedCorner] = useState(false);
|
const [isReversedCorner, setIsReversedCorner] = useState(false);
|
||||||
const [overwrittenWidth, setOverwrittenWidth] = useState<number | undefined>(undefined);
|
const [overwrittenWidth, setOverwrittenWidth] = useState<number | undefined>(undefined);
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import {
|
|||||||
selectChatOnlineCount,
|
selectChatOnlineCount,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
selectThreadMessagesCount,
|
selectThreadMessagesCount,
|
||||||
|
selectTopic,
|
||||||
selectUser,
|
selectUser,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
@ -267,7 +268,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const threadInfo = threadId ? selectThreadInfo(global, chatId, threadId) : undefined;
|
const threadInfo = threadId ? selectThreadInfo(global, chatId, threadId) : undefined;
|
||||||
const onlineCount = chat ? selectChatOnlineCount(global, chat) : undefined;
|
const onlineCount = chat ? selectChatOnlineCount(global, chat) : undefined;
|
||||||
const areMessagesLoaded = Boolean(selectChatMessages(global, chatId));
|
const areMessagesLoaded = Boolean(selectChatMessages(global, chatId));
|
||||||
const topic = threadId ? chat?.topics?.[threadId] : undefined;
|
const topic = threadId ? selectTopic(global, chatId, threadId) : undefined;
|
||||||
const messagesCount = topic && selectThreadMessagesCount(global, chatId, threadId!);
|
const messagesCount = topic && selectThreadMessagesCount(global, chatId, threadId!);
|
||||||
const self = selectUser(global, global.currentUserId!);
|
const self = selectUser(global, global.currentUserId!);
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import {
|
|||||||
selectPeerPhotos,
|
selectPeerPhotos,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectThreadMessagesCount,
|
selectThreadMessagesCount,
|
||||||
|
selectTopic,
|
||||||
selectUser,
|
selectUser,
|
||||||
selectUserStatus,
|
selectUserStatus,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
@ -373,7 +374,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { mediaIndex, chatId: avatarOwnerId } = selectTabState(global).mediaViewer;
|
const { mediaIndex, chatId: avatarOwnerId } = selectTabState(global).mediaViewer;
|
||||||
const isForum = chat?.isForum;
|
const isForum = chat?.isForum;
|
||||||
const { threadId: currentTopicId } = selectCurrentMessageList(global) || {};
|
const { threadId: currentTopicId } = selectCurrentMessageList(global) || {};
|
||||||
const topic = isForum && currentTopicId ? chat?.topics?.[currentTopicId] : undefined;
|
const topic = isForum && currentTopicId ? selectTopic(global, peerId, currentTopicId) : undefined;
|
||||||
|
|
||||||
const emojiStatus = (user || chat)?.emojiStatus;
|
const emojiStatus = (user || chat)?.emojiStatus;
|
||||||
const emojiStatusSticker = emojiStatus ? global.customEmojis.byId[emojiStatus.documentId] : undefined;
|
const emojiStatusSticker = emojiStatus ? global.customEmojis.byId[emojiStatus.documentId] : undefined;
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { getGlobal, withGlobal } from '../../global';
|
|||||||
|
|
||||||
import type { ApiChatType } from '../../api/types';
|
import type { ApiChatType } from '../../api/types';
|
||||||
import type { ThreadId } from '../../types';
|
import type { ThreadId } from '../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
|
||||||
|
|
||||||
import { API_CHAT_TYPES } from '../../config';
|
import { API_CHAT_TYPES } from '../../config';
|
||||||
import {
|
import {
|
||||||
@ -80,7 +79,7 @@ const RecipientPicker: FC<OwnProps & StateProps> = ({
|
|||||||
const user = usersById[id];
|
const user = usersById[id];
|
||||||
if (user && isDeletedUser(user)) return false;
|
if (user && isDeletedUser(user)) return false;
|
||||||
|
|
||||||
return chat && getCanPostInChat(chat, MAIN_THREAD_ID, undefined, chatFullInfoById[id]);
|
return chat && getCanPostInChat(chat, undefined, undefined, chatFullInfoById[id]);
|
||||||
});
|
});
|
||||||
|
|
||||||
const sorted = sortChatIds(
|
const sorted = sortChatIds(
|
||||||
|
|||||||
@ -19,6 +19,7 @@ import {
|
|||||||
selectShouldSchedule,
|
selectShouldSchedule,
|
||||||
selectStickerSet,
|
selectStickerSet,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
|
selectTopic,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { copyTextToClipboard } from '../../util/clipboard';
|
import { copyTextToClipboard } from '../../util/clipboard';
|
||||||
@ -263,8 +264,9 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const sendOptions = chat ? getAllowedAttachmentOptions(chat, chatFullInfo) : undefined;
|
const sendOptions = chat ? getAllowedAttachmentOptions(chat, chatFullInfo) : undefined;
|
||||||
const threadInfo = chatId && threadId ? selectThreadInfo(global, chatId, threadId) : undefined;
|
const threadInfo = chatId && threadId ? selectThreadInfo(global, chatId, threadId) : undefined;
|
||||||
const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
|
const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
|
||||||
|
const topic = chatId && threadId ? selectTopic(global, chatId, threadId) : undefined;
|
||||||
const canSendStickers = Boolean(
|
const canSendStickers = Boolean(
|
||||||
chat && threadId && getCanPostInChat(chat, threadId, isMessageThread, chatFullInfo)
|
chat && threadId && getCanPostInChat(chat, topic, isMessageThread, chatFullInfo)
|
||||||
&& sendOptions?.canSendStickers,
|
&& sendOptions?.canSendStickers,
|
||||||
);
|
);
|
||||||
const isSavedMessages = Boolean(chatId) && selectIsChatWithSelf(global, chatId);
|
const isSavedMessages = Boolean(chatId) && selectIsChatWithSelf(global, chatId);
|
||||||
|
|||||||
@ -13,7 +13,7 @@ import {
|
|||||||
getCanPostInChat, getGroupStatus, getUserStatus, isUserOnline,
|
getCanPostInChat, getGroupStatus, getUserStatus, isUserOnline,
|
||||||
} from '../../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
import { isApiPeerChat } from '../../../global/helpers/peers';
|
import { isApiPeerChat } from '../../../global/helpers/peers';
|
||||||
import { selectChat, selectPeer, selectUserStatus } from '../../../global/selectors';
|
import { selectPeer, selectTopics, selectUserStatus } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { REM } from '../helpers/mediaDimensions';
|
import { REM } from '../helpers/mediaDimensions';
|
||||||
import renderText from '../helpers/renderText';
|
import renderText from '../helpers/renderText';
|
||||||
@ -94,15 +94,15 @@ const ChatOrUserPicker: FC<OwnProps> = ({
|
|||||||
useInputFocusOnOpen(searchRef, isOpen && activeKey === CHAT_LIST_SLIDE, resetSearch);
|
useInputFocusOnOpen(searchRef, isOpen && activeKey === CHAT_LIST_SLIDE, resetSearch);
|
||||||
useInputFocusOnOpen(topicSearchRef, isOpen && activeKey === TOPIC_LIST_SLIDE);
|
useInputFocusOnOpen(topicSearchRef, isOpen && activeKey === TOPIC_LIST_SLIDE);
|
||||||
|
|
||||||
const selectTopics = useLastCallback((global: GlobalState) => {
|
const selectTopicsById = useLastCallback((global: GlobalState) => {
|
||||||
if (!forumId) {
|
if (!forumId) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return selectChat(global, forumId)?.topics;
|
return selectTopics(global, forumId);
|
||||||
});
|
});
|
||||||
|
|
||||||
const forumTopics = useSelector(selectTopics);
|
const forumTopicsById = useSelector(selectTopicsById);
|
||||||
|
|
||||||
const [topicIds, topics] = useMemo(() => {
|
const [topicIds, topics] = useMemo(() => {
|
||||||
const global = getGlobal();
|
const global = getGlobal();
|
||||||
@ -111,16 +111,16 @@ const ChatOrUserPicker: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const chat = chatsById[forumId!];
|
const chat = chatsById[forumId!];
|
||||||
|
|
||||||
if (!chat || !forumTopics) {
|
if (!chat || !forumTopicsById) {
|
||||||
return [undefined, undefined];
|
return [undefined, undefined];
|
||||||
}
|
}
|
||||||
|
|
||||||
const searchTitle = topicSearch.toLowerCase();
|
const searchTitle = topicSearch.toLowerCase();
|
||||||
|
|
||||||
const result = forumTopics
|
const result = forumTopicsById
|
||||||
? Object.values(forumTopics).reduce((acc, topic) => {
|
? Object.values(forumTopicsById).reduce((acc, topic) => {
|
||||||
if (
|
if (
|
||||||
getCanPostInChat(chat, topic.id, undefined, chatFullInfoById[forumId!])
|
getCanPostInChat(chat, topic, undefined, chatFullInfoById[forumId!])
|
||||||
&& (!searchTitle || topic.title.toLowerCase().includes(searchTitle))
|
&& (!searchTitle || topic.title.toLowerCase().includes(searchTitle))
|
||||||
) {
|
) {
|
||||||
acc[topic.id] = topic;
|
acc[topic.id] = topic;
|
||||||
@ -128,10 +128,10 @@ const ChatOrUserPicker: FC<OwnProps> = ({
|
|||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Record<number, ApiTopic>)
|
}, {} as Record<number, ApiTopic>)
|
||||||
: forumTopics;
|
: forumTopicsById;
|
||||||
|
|
||||||
return [Object.keys(result).map(Number), result];
|
return [Object.keys(result).map(Number), result];
|
||||||
}, [forumId, topicSearch, forumTopics]);
|
}, [forumId, topicSearch, forumTopicsById]);
|
||||||
|
|
||||||
const handleHeaderBackClick = useLastCallback(() => {
|
const handleHeaderBackClick = useLastCallback(() => {
|
||||||
setForumId(undefined);
|
setForumId(undefined);
|
||||||
@ -153,7 +153,7 @@ const ChatOrUserPicker: FC<OwnProps> = ({
|
|||||||
const chatId = viewportIds[index === -1 ? 0 : index];
|
const chatId = viewportIds[index === -1 ? 0 : index];
|
||||||
const chat = chatsById[chatId];
|
const chat = chatsById[chatId];
|
||||||
if (chat?.isForum) {
|
if (chat?.isForum) {
|
||||||
if (!chat.topics) loadTopics({ chatId });
|
if (!forumTopicsById) loadTopics({ chatId });
|
||||||
setForumId(chatId);
|
setForumId(chatId);
|
||||||
} else {
|
} else {
|
||||||
onSelectChatOrUser(chatId);
|
onSelectChatOrUser(chatId);
|
||||||
@ -171,7 +171,7 @@ const ChatOrUserPicker: FC<OwnProps> = ({
|
|||||||
const chatsById = getGlobal().chats.byId;
|
const chatsById = getGlobal().chats.byId;
|
||||||
const chat = chatsById?.[chatId];
|
const chat = chatsById?.[chatId];
|
||||||
if (chat?.isForum) {
|
if (chat?.isForum) {
|
||||||
if (!chat.topics) loadTopics({ chatId });
|
if (!forumTopicsById) loadTopics({ chatId });
|
||||||
setForumId(chatId);
|
setForumId(chatId);
|
||||||
resetSearch();
|
resetSearch();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@ -43,6 +43,7 @@ import {
|
|||||||
selectTabState,
|
selectTabState,
|
||||||
selectThreadParam,
|
selectThreadParam,
|
||||||
selectTopicFromMessage,
|
selectTopicFromMessage,
|
||||||
|
selectTopicsInfo,
|
||||||
selectUser,
|
selectUser,
|
||||||
selectUserStatus,
|
selectUserStatus,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
@ -90,6 +91,8 @@ type OwnProps = {
|
|||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
|
listedTopicIds?: number[];
|
||||||
|
topics?: Record<number, ApiTopic>;
|
||||||
isMuted?: boolean;
|
isMuted?: boolean;
|
||||||
user?: ApiUser;
|
user?: ApiUser;
|
||||||
userStatus?: ApiUserStatus;
|
userStatus?: ApiUserStatus;
|
||||||
@ -118,6 +121,8 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
orderDiff,
|
orderDiff,
|
||||||
animationType,
|
animationType,
|
||||||
isPinned,
|
isPinned,
|
||||||
|
listedTopicIds,
|
||||||
|
topics,
|
||||||
observeIntersection,
|
observeIntersection,
|
||||||
chat,
|
chat,
|
||||||
isMuted,
|
isMuted,
|
||||||
@ -190,6 +195,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
orderDiff,
|
orderDiff,
|
||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
isPreview,
|
isPreview,
|
||||||
|
topics,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getIsForumPanelClosed = useSelectorSignal(selectIsForumPanelClosed);
|
const getIsForumPanelClosed = useSelectorSignal(selectIsForumPanelClosed);
|
||||||
@ -284,10 +290,10 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
// Load the forum topics to display unread count badge
|
// Load the forum topics to display unread count badge
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isIntersecting && isForum && chat && chat.listedTopicIds === undefined) {
|
if (isIntersecting && isForum && listedTopicIds === undefined) {
|
||||||
loadTopics({ chatId });
|
loadTopics({ chatId });
|
||||||
}
|
}
|
||||||
}, [chat, chatId, isForum, isIntersecting]);
|
}, [chatId, listedTopicIds, isForum, isIntersecting]);
|
||||||
|
|
||||||
const isOnline = user && userStatus && isUserOnline(user, userStatus);
|
const isOnline = user && userStatus && isUserOnline(user, userStatus);
|
||||||
const { hasShownClass: isAvatarOnlineShown } = useShowTransitionDeprecated(isOnline);
|
const { hasShownClass: isAvatarOnlineShown } = useShowTransitionDeprecated(isOnline);
|
||||||
@ -343,7 +349,13 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
<div className="avatar-badge-wrapper">
|
<div className="avatar-badge-wrapper">
|
||||||
<div className={buildClassName('avatar-online', isAvatarOnlineShown && 'avatar-online-shown')} />
|
<div className={buildClassName('avatar-online', isAvatarOnlineShown && 'avatar-online-shown')} />
|
||||||
<ChatBadge chat={chat} isMuted={isMuted} shouldShowOnlyMostImportant forceHidden={getIsForumPanelClosed} />
|
<ChatBadge
|
||||||
|
chat={chat}
|
||||||
|
isMuted={isMuted}
|
||||||
|
shouldShowOnlyMostImportant
|
||||||
|
forceHidden={getIsForumPanelClosed}
|
||||||
|
topics={topics}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{chat.isCallActive && chat.isCallNotEmpty && (
|
{chat.isCallActive && chat.isCallNotEmpty && (
|
||||||
<ChatCallStatus isMobile={isMobile} isSelected={isSelected} isActive={withInterfaceAnimations} />
|
<ChatCallStatus isMobile={isMobile} isSelected={isSelected} isActive={withInterfaceAnimations} />
|
||||||
@ -376,6 +388,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
isPinned={isPinned}
|
isPinned={isPinned}
|
||||||
isMuted={isMuted}
|
isMuted={isMuted}
|
||||||
isSavedDialog={isSavedDialog}
|
isSavedDialog={isSavedDialog}
|
||||||
|
topics={topics}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -460,6 +473,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const typingStatus = selectThreadParam(global, chatId, MAIN_THREAD_ID, 'typingStatus');
|
const typingStatus = selectThreadParam(global, chatId, MAIN_THREAD_ID, 'typingStatus');
|
||||||
|
|
||||||
|
const topicsInfo = selectTopicsInfo(global, chatId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chat,
|
chat,
|
||||||
isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)),
|
isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)),
|
||||||
@ -484,6 +499,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
lastMessage,
|
lastMessage,
|
||||||
lastMessageId,
|
lastMessageId,
|
||||||
currentUserId: global.currentUserId!,
|
currentUserId: global.currentUserId!,
|
||||||
|
listedTopicIds: topicsInfo?.listedTopicIds,
|
||||||
|
topics: topicsInfo?.topicsById,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(Chat));
|
)(Chat));
|
||||||
|
|||||||
@ -24,10 +24,19 @@ type OwnProps = {
|
|||||||
isSavedDialog?: boolean;
|
isSavedDialog?: boolean;
|
||||||
shouldShowOnlyMostImportant?: boolean;
|
shouldShowOnlyMostImportant?: boolean;
|
||||||
forceHidden?: boolean | Signal<boolean>;
|
forceHidden?: boolean | Signal<boolean>;
|
||||||
|
topics?: Record<number, ApiTopic>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ChatBadge: FC<OwnProps> = ({
|
const ChatBadge: FC<OwnProps> = ({
|
||||||
topic, chat, isPinned, isMuted, shouldShowOnlyMostImportant, wasTopicOpened, forceHidden, isSavedDialog,
|
topic,
|
||||||
|
topics,
|
||||||
|
chat,
|
||||||
|
isPinned,
|
||||||
|
isMuted,
|
||||||
|
shouldShowOnlyMostImportant,
|
||||||
|
wasTopicOpened,
|
||||||
|
forceHidden,
|
||||||
|
isSavedDialog,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
unreadMentionsCount = 0, unreadReactionsCount = 0,
|
unreadMentionsCount = 0, unreadReactionsCount = 0,
|
||||||
@ -36,8 +45,8 @@ const ChatBadge: FC<OwnProps> = ({
|
|||||||
const isTopicUnopened = !isPinned && topic && !wasTopicOpened;
|
const isTopicUnopened = !isPinned && topic && !wasTopicOpened;
|
||||||
const isForum = chat.isForum && !topic;
|
const isForum = chat.isForum && !topic;
|
||||||
const topicsWithUnread = useMemo(() => (
|
const topicsWithUnread = useMemo(() => (
|
||||||
isForum && chat?.topics ? Object.values(chat.topics).filter(({ unreadCount }) => unreadCount) : undefined
|
isForum && topics ? Object.values(topics).filter(({ unreadCount }) => unreadCount) : undefined
|
||||||
), [chat, isForum]);
|
), [topics, isForum]);
|
||||||
|
|
||||||
const unreadCount = useMemo(() => (
|
const unreadCount = useMemo(() => (
|
||||||
isForum
|
isForum
|
||||||
@ -48,11 +57,11 @@ const ChatBadge: FC<OwnProps> = ({
|
|||||||
), [chat, topic, topicsWithUnread, isForum, isMuted]);
|
), [chat, topic, topicsWithUnread, isForum, isMuted]);
|
||||||
|
|
||||||
const shouldBeMuted = useMemo(() => {
|
const shouldBeMuted = useMemo(() => {
|
||||||
const hasUnmutedUnreadTopics = chat.topics
|
const hasUnmutedUnreadTopics = topics
|
||||||
&& Object.values(chat.topics).some((acc) => !acc.isMuted && acc.unreadCount);
|
&& Object.values(topics).some((acc) => !acc.isMuted && acc.unreadCount);
|
||||||
|
|
||||||
return isMuted || (chat.topics && !hasUnmutedUnreadTopics);
|
return isMuted || (topics && !hasUnmutedUnreadTopics);
|
||||||
}, [chat, isMuted]);
|
}, [topics, isMuted]);
|
||||||
|
|
||||||
const hasUnreadMark = topic ? false : chat.hasUnreadMark;
|
const hasUnreadMark = topic ? false : chat.hasUnreadMark;
|
||||||
|
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import React, {
|
|||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiChat } from '../../../api/types';
|
import type { ApiChat } from '../../../api/types';
|
||||||
|
import type { TopicsInfo } from '../../../global/types';
|
||||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -14,7 +15,12 @@ import {
|
|||||||
import { requestNextMutation } from '../../../lib/fasterdom/fasterdom';
|
import { requestNextMutation } from '../../../lib/fasterdom/fasterdom';
|
||||||
import { getOrderedTopics } from '../../../global/helpers';
|
import { getOrderedTopics } from '../../../global/helpers';
|
||||||
import {
|
import {
|
||||||
selectCanAnimateInterface, selectChat, selectCurrentMessageList, selectIsForumPanelOpen, selectTabState,
|
selectCanAnimateInterface,
|
||||||
|
selectChat,
|
||||||
|
selectCurrentMessageList,
|
||||||
|
selectIsForumPanelOpen,
|
||||||
|
selectTabState,
|
||||||
|
selectTopicsInfo,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||||
@ -52,6 +58,7 @@ type OwnProps = {
|
|||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
|
topicsInfo?: TopicsInfo;
|
||||||
currentTopicId?: number;
|
currentTopicId?: number;
|
||||||
withInterfaceAnimations?: boolean;
|
withInterfaceAnimations?: boolean;
|
||||||
};
|
};
|
||||||
@ -63,6 +70,7 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
|
|||||||
currentTopicId,
|
currentTopicId,
|
||||||
isOpen,
|
isOpen,
|
||||||
isHidden,
|
isHidden,
|
||||||
|
topicsInfo,
|
||||||
onTopicSearch,
|
onTopicSearch,
|
||||||
onCloseAnimationEnd,
|
onCloseAnimationEnd,
|
||||||
onOpenAnimationStart,
|
onOpenAnimationStart,
|
||||||
@ -80,12 +88,13 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
|
|||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const scrollTopHandlerRef = useRef<HTMLDivElement>(null);
|
const scrollTopHandlerRef = useRef<HTMLDivElement>(null);
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
|
const chatId = chat?.id;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (chat && !chat.topics) {
|
if (chatId && !topicsInfo) {
|
||||||
loadTopics({ chatId: chat.id });
|
loadTopics({ chatId });
|
||||||
}
|
}
|
||||||
}, [chat, loadTopics]);
|
}, [topicsInfo, chatId]);
|
||||||
|
|
||||||
const [isScrolled, setIsScrolled] = useState(false);
|
const [isScrolled, setIsScrolled] = useState(false);
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
@ -115,17 +124,20 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const orderedIds = useMemo(() => {
|
const orderedIds = useMemo(() => {
|
||||||
return chat?.topics
|
return topicsInfo
|
||||||
? getOrderedTopics(Object.values(chat.topics), chat.orderedPinnedTopicIds).map(({ id }) => id)
|
? getOrderedTopics(
|
||||||
|
Object.values(topicsInfo.topicsById),
|
||||||
|
topicsInfo.orderedPinnedTopicIds,
|
||||||
|
).map(({ id }) => id)
|
||||||
: [];
|
: [];
|
||||||
}, [chat]);
|
}, [topicsInfo]);
|
||||||
|
|
||||||
const { orderDiffById, getAnimationType } = useOrderDiff(orderedIds, chat?.id);
|
const { orderDiffById, getAnimationType } = useOrderDiff(orderedIds, chat?.id);
|
||||||
|
|
||||||
const [viewportIds, getMore] = useInfiniteScroll(() => {
|
const [viewportIds, getMore] = useInfiniteScroll(() => {
|
||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
loadTopics({ chatId: chat.id });
|
loadTopics({ chatId: chat.id });
|
||||||
}, orderedIds, !chat?.topicsCount || orderedIds.length >= chat.topicsCount, TOPICS_SLICE);
|
}, orderedIds, !topicsInfo?.totalCount || orderedIds.length >= topicsInfo.totalCount, TOPICS_SLICE);
|
||||||
|
|
||||||
const shouldRenderRef = useRef(false);
|
const shouldRenderRef = useRef(false);
|
||||||
const isVisible = isOpen && !isHidden;
|
const isVisible = isOpen && !isHidden;
|
||||||
@ -191,7 +203,7 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
|
|||||||
<Topic
|
<Topic
|
||||||
key={id}
|
key={id}
|
||||||
chatId={chat!.id}
|
chatId={chat!.id}
|
||||||
topic={chat!.topics![id]}
|
topic={topicsInfo!.topicsById[id]}
|
||||||
style={`top: ${(viewportOffset + i) * TOPIC_HEIGHT_PX}px;`}
|
style={`top: ${(viewportOffset + i) * TOPIC_HEIGHT_PX}px;`}
|
||||||
isSelected={currentTopicId === id}
|
isSelected={currentTopicId === id}
|
||||||
observeIntersection={observe}
|
observeIntersection={observe}
|
||||||
@ -201,7 +213,7 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLoading = chat?.topics === undefined;
|
const isLoading = topicsInfo === undefined;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -271,7 +283,7 @@ const ForumPanel: FC<OwnProps & StateProps> = ({
|
|||||||
)}
|
)}
|
||||||
</InfiniteScroll>
|
</InfiniteScroll>
|
||||||
{!isLoading && viewportIds?.length === 1 && viewportIds[0] === GENERAL_TOPIC_ID && (
|
{!isLoading && viewportIds?.length === 1 && viewportIds[0] === GENERAL_TOPIC_ID && (
|
||||||
<EmptyForum chatId={chat.id} />
|
<EmptyForum chatId={chatId!} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -285,11 +297,13 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
chatId: currentChatId,
|
chatId: currentChatId,
|
||||||
threadId: currentThreadId,
|
threadId: currentThreadId,
|
||||||
} = selectCurrentMessageList(global) || {};
|
} = selectCurrentMessageList(global) || {};
|
||||||
|
const topicsInfo = chatId ? selectTopicsInfo(global, chatId) : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chat,
|
chat,
|
||||||
currentTopicId: chatId === currentChatId ? Number(currentThreadId) : undefined,
|
currentTopicId: chatId === currentChatId ? Number(currentThreadId) : undefined,
|
||||||
withInterfaceAnimations: selectCanAnimateInterface(global),
|
withInterfaceAnimations: selectCanAnimateInterface(global),
|
||||||
|
topicsInfo,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(global) => selectIsForumPanelOpen(global),
|
(global) => selectIsForumPanelOpen(global),
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import {
|
|||||||
selectOutgoingStatus,
|
selectOutgoingStatus,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
selectThreadParam,
|
selectThreadParam,
|
||||||
|
selectTopics,
|
||||||
selectUser,
|
selectUser,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
@ -68,6 +69,7 @@ type StateProps = {
|
|||||||
canScrollDown?: boolean;
|
canScrollDown?: boolean;
|
||||||
wasTopicOpened?: boolean;
|
wasTopicOpened?: boolean;
|
||||||
withInterfaceAnimations?: boolean;
|
withInterfaceAnimations?: boolean;
|
||||||
|
topics?: Record<number, ApiTopic>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Topic: FC<OwnProps & StateProps> = ({
|
const Topic: FC<OwnProps & StateProps> = ({
|
||||||
@ -91,6 +93,7 @@ const Topic: FC<OwnProps & StateProps> = ({
|
|||||||
typingStatus,
|
typingStatus,
|
||||||
draft,
|
draft,
|
||||||
wasTopicOpened,
|
wasTopicOpened,
|
||||||
|
topics,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
openThread,
|
openThread,
|
||||||
@ -138,6 +141,7 @@ const Topic: FC<OwnProps & StateProps> = ({
|
|||||||
observeIntersection,
|
observeIntersection,
|
||||||
isTopic: true,
|
isTopic: true,
|
||||||
typingStatus,
|
typingStatus,
|
||||||
|
topics,
|
||||||
|
|
||||||
animationType,
|
animationType,
|
||||||
withInterfaceAnimations,
|
withInterfaceAnimations,
|
||||||
@ -208,6 +212,7 @@ const Topic: FC<OwnProps & StateProps> = ({
|
|||||||
isMuted={isMuted}
|
isMuted={isMuted}
|
||||||
topic={topic}
|
topic={topic}
|
||||||
wasTopicOpened={wasTopicOpened}
|
wasTopicOpened={wasTopicOpened}
|
||||||
|
topics={topics}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -253,6 +258,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const draft = selectDraft(global, chatId, topic.id);
|
const draft = selectDraft(global, chatId, topic.id);
|
||||||
const threadInfo = selectThreadInfo(global, chatId, topic.id);
|
const threadInfo = selectThreadInfo(global, chatId, topic.id);
|
||||||
const wasTopicOpened = Boolean(threadInfo?.lastReadInboxMessageId);
|
const wasTopicOpened = Boolean(threadInfo?.lastReadInboxMessageId);
|
||||||
|
const topics = selectTopics(global, chatId);
|
||||||
|
|
||||||
const { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global) || {};
|
const { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global) || {};
|
||||||
|
|
||||||
@ -272,6 +278,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
}),
|
}),
|
||||||
canScrollDown: isSelected && chat?.id === currentChatId && currentThreadId === topic.id,
|
canScrollDown: isSelected && chat?.id === currentChatId && currentThreadId === topic.id,
|
||||||
wasTopicOpened,
|
wasTopicOpened,
|
||||||
|
topics,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(Topic));
|
)(Topic));
|
||||||
|
|||||||
@ -43,6 +43,7 @@ const ANIMATION_DURATION = 200;
|
|||||||
|
|
||||||
export default function useChatListEntry({
|
export default function useChatListEntry({
|
||||||
chat,
|
chat,
|
||||||
|
topics,
|
||||||
lastMessage,
|
lastMessage,
|
||||||
chatId,
|
chatId,
|
||||||
typingStatus,
|
typingStatus,
|
||||||
@ -61,6 +62,7 @@ export default function useChatListEntry({
|
|||||||
isPreview,
|
isPreview,
|
||||||
}: {
|
}: {
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
|
topics?: Record<number, ApiTopic>;
|
||||||
lastMessage?: ApiMessage;
|
lastMessage?: ApiMessage;
|
||||||
chatId: string;
|
chatId: string;
|
||||||
typingStatus?: ApiTypingStatus;
|
typingStatus?: ApiTypingStatus;
|
||||||
@ -192,6 +194,7 @@ export default function useChatListEntry({
|
|||||||
chat={chat}
|
chat={chat}
|
||||||
renderLastMessage={renderLastMessageOrTyping}
|
renderLastMessage={renderLastMessageOrTyping}
|
||||||
observeIntersection={observeIntersection}
|
observeIntersection={observeIntersection}
|
||||||
|
topics={topics}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import { withGlobal } from '../../../global';
|
|||||||
|
|
||||||
import type { ApiTopic } from '../../../api/types';
|
import type { ApiTopic } from '../../../api/types';
|
||||||
|
|
||||||
import { selectChat } from '../../../global/selectors';
|
import { selectTopic } from '../../../global/selectors';
|
||||||
import { REM } from '../../common/helpers/mediaDimensions';
|
import { REM } from '../../common/helpers/mediaDimensions';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
|
||||||
@ -60,8 +60,7 @@ const LeftSearchResultTopic: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { chatId, topicId }): StateProps => {
|
(global, { chatId, topicId }): StateProps => {
|
||||||
const chat = selectChat(global, chatId);
|
const topic = selectTopic(global, chatId, topicId);
|
||||||
const topic = chat?.topics?.[topicId];
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
topic,
|
topic,
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { getActions } from '../../lib/teact/teactn';
|
|||||||
import { withGlobal } from '../../global';
|
import { withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { TabState } from '../../global/types';
|
import type { TabState } from '../../global/types';
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
|
||||||
|
|
||||||
import { getCanPostInChat } from '../../global/helpers';
|
import { getCanPostInChat } from '../../global/helpers';
|
||||||
import { selectChat, selectChatFullInfo } from '../../global/selectors';
|
import { selectChat, selectChatFullInfo } from '../../global/selectors';
|
||||||
@ -94,7 +93,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { chatId } = openedGame || {};
|
const { chatId } = openedGame || {};
|
||||||
const chat = chatId && selectChat(global, chatId);
|
const chat = chatId && selectChat(global, chatId);
|
||||||
const chatFullInfo = chatId ? selectChatFullInfo(global, chatId) : undefined;
|
const chatFullInfo = chatId ? selectChatFullInfo(global, chatId) : undefined;
|
||||||
const canPost = Boolean(chat) && getCanPostInChat(chat, MAIN_THREAD_ID, undefined, chatFullInfo);
|
const canPost = Boolean(chat) && getCanPostInChat(chat, undefined, undefined, chatFullInfo);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
canPost,
|
canPost,
|
||||||
|
|||||||
@ -35,6 +35,7 @@ import {
|
|||||||
selectNotifyExceptions,
|
selectNotifyExceptions,
|
||||||
selectNotifySettings,
|
selectNotifySettings,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
|
selectTopic,
|
||||||
selectUser,
|
selectUser,
|
||||||
selectUserFullInfo,
|
selectUserFullInfo,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
@ -760,7 +761,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
&& !selectIsPremiumPurchaseBlocked(global),
|
&& !selectIsPremiumPurchaseBlocked(global),
|
||||||
);
|
);
|
||||||
|
|
||||||
const topic = chat?.topics?.[threadId];
|
const topic = selectTopic(global, chatId, threadId);
|
||||||
const canCreateTopic = chat.isForum && (
|
const canCreateTopic = chat.isForum && (
|
||||||
chat.isCreator || !isUserRightBanned(chat, 'manageTopics') || getHasAdminRight(chat, 'manageTopics')
|
chat.isCreator || !isUserRightBanned(chat, 'manageTopics') || getHasAdminRight(chat, 'manageTopics')
|
||||||
);
|
);
|
||||||
|
|||||||
@ -54,6 +54,7 @@ import {
|
|||||||
selectScrollOffset,
|
selectScrollOffset,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
|
selectTopic,
|
||||||
selectUserFullInfo,
|
selectUserFullInfo,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import animateScroll, { isAnimatingScroll, restartCurrentScrollAnimation } from '../../util/animateScroll';
|
import animateScroll, { isAnimatingScroll, restartCurrentScrollAnimation } from '../../util/animateScroll';
|
||||||
@ -772,7 +773,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const chatBot = selectBot(global, chatId);
|
const chatBot = selectBot(global, chatId);
|
||||||
|
|
||||||
const topic = chat.topics?.[threadId];
|
const topic = selectTopic(global, chatId, threadId);
|
||||||
const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined;
|
const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined;
|
||||||
const isEmptyThread = !selectThreadInfo(global, chatId, threadId)?.messagesCount;
|
const isEmptyThread = !selectThreadInfo(global, chatId, threadId)?.messagesCount;
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,9 @@ import React, {
|
|||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { ApiChat, ApiChatBannedRights, ApiInputMessageReplyInfo } from '../../api/types';
|
import type {
|
||||||
|
ApiChat, ApiChatBannedRights, ApiInputMessageReplyInfo, ApiTopic,
|
||||||
|
} from '../../api/types';
|
||||||
import type {
|
import type {
|
||||||
ActiveEmojiInteraction,
|
ActiveEmojiInteraction,
|
||||||
MessageListType,
|
MessageListType,
|
||||||
@ -55,6 +57,8 @@ import {
|
|||||||
selectTabState,
|
selectTabState,
|
||||||
selectTheme,
|
selectTheme,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
|
selectTopic,
|
||||||
|
selectTopics,
|
||||||
selectUserFullInfo,
|
selectUserFullInfo,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
@ -156,6 +160,7 @@ type StateProps = {
|
|||||||
isSavedDialog?: boolean;
|
isSavedDialog?: boolean;
|
||||||
canShowOpenChatButton?: boolean;
|
canShowOpenChatButton?: boolean;
|
||||||
isContactRequirePremium?: boolean;
|
isContactRequirePremium?: boolean;
|
||||||
|
topics?: Record<number, ApiTopic>;
|
||||||
};
|
};
|
||||||
|
|
||||||
function isImage(item: DataTransferItem) {
|
function isImage(item: DataTransferItem) {
|
||||||
@ -217,6 +222,7 @@ function MiddleColumn({
|
|||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
canShowOpenChatButton,
|
canShowOpenChatButton,
|
||||||
isContactRequirePremium,
|
isContactRequirePremium,
|
||||||
|
topics,
|
||||||
}: OwnProps & StateProps) {
|
}: OwnProps & StateProps) {
|
||||||
const {
|
const {
|
||||||
openChat,
|
openChat,
|
||||||
@ -455,7 +461,7 @@ function MiddleColumn({
|
|||||||
const messageSendingRestrictionReason = getMessageSendingRestrictionReason(
|
const messageSendingRestrictionReason = getMessageSendingRestrictionReason(
|
||||||
lang, currentUserBannedRights, defaultBannedRights,
|
lang, currentUserBannedRights, defaultBannedRights,
|
||||||
);
|
);
|
||||||
const forumComposerPlaceholder = getForumComposerPlaceholder(lang, chat, threadId, Boolean(draftReplyInfo));
|
const forumComposerPlaceholder = getForumComposerPlaceholder(lang, chat, threadId, topics, Boolean(draftReplyInfo));
|
||||||
|
|
||||||
const composerRestrictionMessage = messageSendingRestrictionReason
|
const composerRestrictionMessage = messageSendingRestrictionReason
|
||||||
?? forumComposerPlaceholder
|
?? forumComposerPlaceholder
|
||||||
@ -775,7 +781,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
||||||
const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
|
const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
|
||||||
const canPost = chat && getCanPostInChat(chat, threadId, isMessageThread, chatFullInfo);
|
const topic = selectTopic(global, chatId, threadId);
|
||||||
|
const canPost = chat && getCanPostInChat(chat, topic, isMessageThread, chatFullInfo);
|
||||||
const isBotNotStarted = selectIsChatBotNotStarted(global, chatId);
|
const isBotNotStarted = selectIsChatBotNotStarted(global, chatId);
|
||||||
const isPinnedMessageList = messageListType === 'pinned';
|
const isPinnedMessageList = messageListType === 'pinned';
|
||||||
const isMainThread = messageListType === 'thread' && threadId === MAIN_THREAD_ID;
|
const isMainThread = messageListType === 'thread' && threadId === MAIN_THREAD_ID;
|
||||||
@ -794,11 +801,12 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
);
|
);
|
||||||
const draftReplyInfo = selectDraft(global, chatId, threadId)?.replyInfo;
|
const draftReplyInfo = selectDraft(global, chatId, threadId)?.replyInfo;
|
||||||
const shouldBlockSendInForum = chat?.isForum
|
const shouldBlockSendInForum = chat?.isForum
|
||||||
? threadId === MAIN_THREAD_ID && !draftReplyInfo && (chat.topics?.[GENERAL_TOPIC_ID]?.isClosed)
|
? threadId === MAIN_THREAD_ID && !draftReplyInfo && (selectTopic(global, chatId, GENERAL_TOPIC_ID)?.isClosed)
|
||||||
: false;
|
: false;
|
||||||
const audioMessage = audioChatId && audioMessageId
|
const audioMessage = audioChatId && audioMessageId
|
||||||
? selectChatMessage(global, audioChatId, audioMessageId)
|
? selectChatMessage(global, audioChatId, audioMessageId)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
const topics = selectTopics(global, chatId);
|
||||||
|
|
||||||
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
|
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
|
||||||
const canShowOpenChatButton = isSavedDialog && threadId !== ANONYMOUS_USER_ID;
|
const canShowOpenChatButton = isSavedDialog && threadId !== ANONYMOUS_USER_ID;
|
||||||
@ -854,6 +862,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
canShowOpenChatButton,
|
canShowOpenChatButton,
|
||||||
isContactRequirePremium,
|
isContactRequirePremium,
|
||||||
|
topics,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(MiddleColumn));
|
)(MiddleColumn));
|
||||||
|
|||||||
@ -54,6 +54,7 @@ import {
|
|||||||
selectRequestedMessageTranslationLanguage,
|
selectRequestedMessageTranslationLanguage,
|
||||||
selectStickerSet,
|
selectStickerSet,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
|
selectTopic,
|
||||||
selectUserStatus,
|
selectUserStatus,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import { copyTextToClipboard } from '../../../util/clipboard';
|
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||||
@ -730,10 +731,11 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const threadInfo = threadId && selectThreadInfo(global, message.chatId, threadId);
|
const threadInfo = threadId && selectThreadInfo(global, message.chatId, threadId);
|
||||||
const isMessageThread = Boolean(threadInfo && !threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
|
const isMessageThread = Boolean(threadInfo && !threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
|
||||||
|
const topic = threadId ? selectTopic(global, message.chatId, threadId) : undefined;
|
||||||
|
|
||||||
const canSendText = chat && !isUserRightBanned(chat, 'sendPlain', chatFullInfo);
|
const canSendText = chat && !isUserRightBanned(chat, 'sendPlain', chatFullInfo);
|
||||||
|
|
||||||
const canReplyInChat = chat && threadId ? getCanPostInChat(chat, threadId, isMessageThread, chatFullInfo)
|
const canReplyInChat = chat && threadId ? getCanPostInChat(chat, topic, isMessageThread, chatFullInfo)
|
||||||
&& canSendText : false;
|
&& canSendText : false;
|
||||||
|
|
||||||
const isLocal = isMessageLocal(message);
|
const isLocal = isMessageLocal(message);
|
||||||
|
|||||||
@ -31,7 +31,7 @@ import type { PinnedIntersectionChangedCallback } from '../hooks/usePinnedMessag
|
|||||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||||
import { AudioOrigin } from '../../../types';
|
import { AudioOrigin } from '../../../types';
|
||||||
|
|
||||||
import { EMOJI_STATUS_LOOP_LIMIT, GENERAL_TOPIC_ID } from '../../../config';
|
import { EMOJI_STATUS_LOOP_LIMIT } from '../../../config';
|
||||||
import {
|
import {
|
||||||
areReactionsEmpty,
|
areReactionsEmpty,
|
||||||
getIsDownloading,
|
getIsDownloading,
|
||||||
@ -1766,8 +1766,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const hasUnreadReaction = chat?.unreadReactions?.includes(message.id);
|
const hasUnreadReaction = chat?.unreadReactions?.includes(message.id);
|
||||||
|
|
||||||
const hasTopicChip = threadId === MAIN_THREAD_ID && chat?.isForum && isFirstInGroup;
|
const hasTopicChip = threadId === MAIN_THREAD_ID && chat?.isForum && isFirstInGroup;
|
||||||
const messageTopic = hasTopicChip ? (selectTopicFromMessage(global, message) || chat?.topics?.[GENERAL_TOPIC_ID])
|
const messageTopic = hasTopicChip ? selectTopicFromMessage(global, message) : undefined;
|
||||||
: undefined;
|
|
||||||
|
|
||||||
const chatTranslations = selectChatTranslations(global, chatId);
|
const chatTranslations = selectChatTranslations(global, chatId);
|
||||||
|
|
||||||
|
|||||||
@ -8,7 +8,9 @@ import type { ApiChat, ApiSticker, ApiTopic } from '../../api/types';
|
|||||||
import type { TabState } from '../../global/types';
|
import type { TabState } from '../../global/types';
|
||||||
|
|
||||||
import { DEFAULT_TOPIC_ICON_STICKER_ID, GENERAL_TOPIC_ID } from '../../config';
|
import { DEFAULT_TOPIC_ICON_STICKER_ID, GENERAL_TOPIC_ID } from '../../config';
|
||||||
import { selectChat, selectIsCurrentUserPremium, selectTabState } from '../../global/selectors';
|
import {
|
||||||
|
selectChat, selectIsCurrentUserPremium, selectTabState, selectTopic,
|
||||||
|
} from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { REM } from '../common/helpers/mediaDimensions';
|
import { REM } from '../common/helpers/mediaDimensions';
|
||||||
|
|
||||||
@ -185,7 +187,8 @@ export default memo(withGlobal(
|
|||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
const { editTopicPanel } = selectTabState(global);
|
const { editTopicPanel } = selectTabState(global);
|
||||||
const chat = editTopicPanel?.chatId ? selectChat(global, editTopicPanel.chatId) : undefined;
|
const chat = editTopicPanel?.chatId ? selectChat(global, editTopicPanel.chatId) : undefined;
|
||||||
const topic = editTopicPanel?.topicId ? chat?.topics?.[editTopicPanel?.topicId] : undefined;
|
const topic = editTopicPanel?.chatId && editTopicPanel?.topicId
|
||||||
|
? selectTopic(global, editTopicPanel.chatId, editTopicPanel.topicId) : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chat,
|
chat,
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import {
|
|||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectIsChatWithBot,
|
selectIsChatWithBot,
|
||||||
selectIsChatWithSelf, selectThreadInfo,
|
selectIsChatWithSelf, selectThreadInfo,
|
||||||
|
selectTopic,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
|
import { IS_TOUCH_ENV } from '../../util/windowEnvironment';
|
||||||
@ -174,8 +175,9 @@ export default memo(withGlobal(
|
|||||||
const isSavedMessages = Boolean(chatId) && selectIsChatWithSelf(global, chatId);
|
const isSavedMessages = Boolean(chatId) && selectIsChatWithSelf(global, chatId);
|
||||||
const threadInfo = chatId && threadId ? selectThreadInfo(global, chatId, threadId) : undefined;
|
const threadInfo = chatId && threadId ? selectThreadInfo(global, chatId, threadId) : undefined;
|
||||||
const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
|
const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
|
||||||
|
const topic = chatId && threadId ? selectTopic(global, chatId, threadId) : undefined;
|
||||||
const canPostInChat = Boolean(chat) && Boolean(threadId)
|
const canPostInChat = Boolean(chat) && Boolean(threadId)
|
||||||
&& getCanPostInChat(chat, threadId, isMessageThread, chatFullInfo);
|
&& getCanPostInChat(chat, topic, isMessageThread, chatFullInfo);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
query,
|
query,
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import {
|
|||||||
selectCurrentStickerSearch,
|
selectCurrentStickerSearch,
|
||||||
selectIsChatWithSelf,
|
selectIsChatWithSelf,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
|
selectTopic,
|
||||||
selectUser,
|
selectUser,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
@ -582,7 +583,7 @@ export default withGlobal<OwnProps>(
|
|||||||
const user = isProfile && chatId && isUserId(chatId) ? selectUser(global, chatId) : undefined;
|
const user = isProfile && chatId && isUserId(chatId) ? selectUser(global, chatId) : undefined;
|
||||||
const isChannel = chat && isChatChannel(chat);
|
const isChannel = chat && isChatChannel(chat);
|
||||||
const isInsideTopic = chat?.isForum && Boolean(threadId && threadId !== MAIN_THREAD_ID);
|
const isInsideTopic = chat?.isForum && Boolean(threadId && threadId !== MAIN_THREAD_ID);
|
||||||
const topic = isInsideTopic ? chat.topics?.[threadId!] : undefined;
|
const topic = isInsideTopic ? selectTopic(global, chatId!, threadId!) : undefined;
|
||||||
const canEditTopic = isInsideTopic && topic && getCanManageTopic(chat, topic);
|
const canEditTopic = isInsideTopic && topic && getCanManageTopic(chat, topic);
|
||||||
const isBot = user && isUserBot(user);
|
const isBot = user && isUserBot(user);
|
||||||
const isSavedMessages = chatId ? selectIsChatWithSelf(global, chatId) : undefined;
|
const isSavedMessages = chatId ? selectIsChatWithSelf(global, chatId) : undefined;
|
||||||
|
|||||||
@ -112,6 +112,9 @@ import {
|
|||||||
selectTabState,
|
selectTabState,
|
||||||
selectThread,
|
selectThread,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
|
selectTopic,
|
||||||
|
selectTopics,
|
||||||
|
selectTopicsInfo,
|
||||||
selectUser,
|
selectUser,
|
||||||
selectUserByPhoneNumber,
|
selectUserByPhoneNumber,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
@ -1169,7 +1172,9 @@ addActionHandler('markTopicRead', (global, actions, payload): ActionReturnType =
|
|||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
|
|
||||||
const lastTopicMessageId = chat.topics?.[topicId]?.lastMessageId;
|
const topic = selectTopic(global, chatId, topicId);
|
||||||
|
|
||||||
|
const lastTopicMessageId = topic?.lastMessageId;
|
||||||
if (!lastTopicMessageId) return;
|
if (!lastTopicMessageId) return;
|
||||||
|
|
||||||
void callApi('markMessageListRead', {
|
void callApi('markMessageListRead', {
|
||||||
@ -2074,13 +2079,15 @@ addActionHandler('loadTopics', async (global, actions, payload): Promise<void> =
|
|||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
|
|
||||||
if (!force && chat.listedTopicIds && chat.listedTopicIds.length === chat.topicsCount) {
|
const topicsInfo = selectTopicsInfo(global, chatId);
|
||||||
|
|
||||||
|
if (!force && topicsInfo?.listedTopicIds && topicsInfo.listedTopicIds.length === topicsInfo.totalCount) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const offsetTopic = !force && chat.listedTopicIds ? chat.listedTopicIds.reduce((acc, el) => {
|
const offsetTopic = !force ? topicsInfo?.listedTopicIds?.reduce((acc, el) => {
|
||||||
const topic = chat.topics?.[el];
|
const topic = selectTopic(global, chatId, el);
|
||||||
const accTopic = chat.topics?.[acc];
|
const accTopic = selectTopic(global, chatId, acc);
|
||||||
if (!topic) return acc;
|
if (!topic) return acc;
|
||||||
if (!accTopic || topic.lastMessageId < accTopic.lastMessageId) {
|
if (!accTopic || topic.lastMessageId < accTopic.lastMessageId) {
|
||||||
return el;
|
return el;
|
||||||
@ -2089,7 +2096,7 @@ addActionHandler('loadTopics', async (global, actions, payload): Promise<void> =
|
|||||||
}) : undefined;
|
}) : undefined;
|
||||||
|
|
||||||
const { id: offsetTopicId, date: offsetDate, lastMessageId: offsetId } = (offsetTopic
|
const { id: offsetTopicId, date: offsetDate, lastMessageId: offsetId } = (offsetTopic
|
||||||
&& chat.topics?.[offsetTopic]) || {};
|
&& selectTopic(global, chatId, offsetTopic)) || {};
|
||||||
const result = await callApi('fetchTopics', {
|
const result = await callApi('fetchTopics', {
|
||||||
chat, offsetTopicId, offsetId, offsetDate, limit: offsetTopicId ? TOPICS_SLICE : TOPICS_SLICE_SECOND_LOAD,
|
chat, offsetTopicId, offsetId, offsetDate, limit: offsetTopicId ? TOPICS_SLICE : TOPICS_SLICE_SECOND_LOAD,
|
||||||
});
|
});
|
||||||
@ -2231,7 +2238,7 @@ addActionHandler('editTopic', async (global, actions, payload): Promise<void> =>
|
|||||||
chatId, topicId, tabId = getCurrentTabId(), ...rest
|
chatId, topicId, tabId = getCurrentTabId(), ...rest
|
||||||
} = payload;
|
} = payload;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
const topic = chat?.topics?.[topicId];
|
const topic = selectTopic(global, chatId, topicId);
|
||||||
if (!chat || !topic) return;
|
if (!chat || !topic) return;
|
||||||
|
|
||||||
if (selectTabState(global, tabId).editTopicPanel) {
|
if (selectTabState(global, tabId).editTopicPanel) {
|
||||||
@ -2262,9 +2269,10 @@ addActionHandler('toggleTopicPinned', (global, actions, payload): ActionReturnTy
|
|||||||
|
|
||||||
const { topicsPinnedLimit } = global.appConfig || {};
|
const { topicsPinnedLimit } = global.appConfig || {};
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat || !chat.topics || !topicsPinnedLimit) return;
|
const topics = selectTopics(global, chatId);
|
||||||
|
if (!chat || !topics || !topicsPinnedLimit) return;
|
||||||
|
|
||||||
if (isPinned && Object.values(chat.topics).filter((topic) => topic.isPinned).length >= topicsPinnedLimit) {
|
if (isPinned && Object.values(topics).filter((topic) => topic.isPinned).length >= topicsPinnedLimit) {
|
||||||
actions.showNotification({
|
actions.showNotification({
|
||||||
message: langProvider.oldTranslate('LimitReachedPinnedTopics', topicsPinnedLimit, 'i'),
|
message: langProvider.oldTranslate('LimitReachedPinnedTopics', topicsPinnedLimit, 'i'),
|
||||||
tabId,
|
tabId,
|
||||||
|
|||||||
@ -127,6 +127,7 @@ import {
|
|||||||
selectSponsoredMessage,
|
selectSponsoredMessage,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectThreadIdFromMessage,
|
selectThreadIdFromMessage,
|
||||||
|
selectTopic,
|
||||||
selectTranslationLanguage,
|
selectTranslationLanguage,
|
||||||
selectUser,
|
selectUser,
|
||||||
selectUserFullInfo,
|
selectUserFullInfo,
|
||||||
@ -907,8 +908,8 @@ addActionHandler('markMessageListRead', (global, actions, payload): ActionReturn
|
|||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat.isForum && chat.topics?.[threadId]) {
|
const topic = selectTopic(global, chatId, threadId);
|
||||||
const topic = chat.topics[threadId];
|
if (chat.isForum && topic) {
|
||||||
global = updateThreadInfo(global, chatId, threadId, {
|
global = updateThreadInfo(global, chatId, threadId, {
|
||||||
lastReadInboxMessageId: maxId,
|
lastReadInboxMessageId: maxId,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -36,6 +36,7 @@ import {
|
|||||||
selectEditingId,
|
selectEditingId,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
|
selectTopics,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
|
|
||||||
const RELEASE_STATUS_TIMEOUT = 15000; // 15 sec;
|
const RELEASE_STATUS_TIMEOUT = 15000; // 15 sec;
|
||||||
@ -160,10 +161,10 @@ async function loadAndReplaceMessages<T extends GlobalState>(global: T, actions:
|
|||||||
const localMessages = currentChatId === SERVICE_NOTIFICATIONS_USER_ID
|
const localMessages = currentChatId === SERVICE_NOTIFICATIONS_USER_ID
|
||||||
? global.serviceNotifications.filter(({ isDeleted }) => !isDeleted).map(({ message }) => message)
|
? global.serviceNotifications.filter(({ isDeleted }) => !isDeleted).map(({ message }) => message)
|
||||||
: [];
|
: [];
|
||||||
const topicLastMessages = currentChat.isForum && currentChat.topics
|
const topics = selectTopics(global, currentChatId);
|
||||||
? Object.values(currentChat.topics)
|
const topicLastMessages = topics ? Object.values(topics)
|
||||||
.map(({ lastMessageId }) => currentChatMessages[lastMessageId])
|
.map(({ lastMessageId }) => currentChatMessages[lastMessageId])
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const resultMessageIds = result.messages.map(({ id }) => id);
|
const resultMessageIds = result.messages.map(({ id }) => id);
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
leaveChat,
|
leaveChat,
|
||||||
removeUnreadMentions,
|
removeUnreadMentions,
|
||||||
replacePeerPhotos,
|
replacePeerPhotos,
|
||||||
|
replacePinnedTopicIds,
|
||||||
replaceThreadParam,
|
replaceThreadParam,
|
||||||
updateChat,
|
updateChat,
|
||||||
updateChatFullInfo,
|
updateChatFullInfo,
|
||||||
@ -482,9 +483,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
const chat = global.chats.byId[chatId];
|
const chat = global.chats.byId[chatId];
|
||||||
if (!chat) return undefined;
|
if (!chat) return undefined;
|
||||||
|
|
||||||
global = updateChat(global, chatId, {
|
global = replacePinnedTopicIds(global, chatId, order);
|
||||||
orderedPinnedTopicIds: order,
|
|
||||||
});
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
|||||||
@ -78,6 +78,7 @@ import {
|
|||||||
selectThreadByMessage,
|
selectThreadByMessage,
|
||||||
selectThreadIdFromMessage,
|
selectThreadIdFromMessage,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
|
selectTopic,
|
||||||
selectTopicFromMessage,
|
selectTopicFromMessage,
|
||||||
selectViewportIds,
|
selectViewportIds,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
@ -1061,7 +1062,7 @@ export function deleteMessages<T extends GlobalState>(
|
|||||||
isDeleting: true,
|
isDeleting: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (chat.topics?.[id]) {
|
if (selectTopic(global, chatId, id)) {
|
||||||
global = deleteTopic(global, chatId, id);
|
global = deleteTopic(global, chatId, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1091,7 +1092,12 @@ export function deleteMessages<T extends GlobalState>(
|
|||||||
if (!threadInfo?.lastMessageId || !idsSet.has(threadInfo.lastMessageId)) return;
|
if (!threadInfo?.lastMessageId || !idsSet.has(threadInfo.lastMessageId)) return;
|
||||||
|
|
||||||
const newLastMessage = findLastMessage(global, chatId, threadId);
|
const newLastMessage = findLastMessage(global, chatId, threadId);
|
||||||
if (!newLastMessage) return;
|
if (!newLastMessage) {
|
||||||
|
if (chat.isForum && threadId !== MAIN_THREAD_ID) {
|
||||||
|
actions.loadTopicById({ chatId, topicId: Number(threadId) });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (threadId === MAIN_THREAD_ID) {
|
if (threadId === MAIN_THREAD_ID) {
|
||||||
global = updateChatLastMessage(global, chatId, newLastMessage, true);
|
global = updateChatLastMessage(global, chatId, newLastMessage, true);
|
||||||
|
|||||||
@ -33,6 +33,7 @@ import {
|
|||||||
selectIsTrustedBot,
|
selectIsTrustedBot,
|
||||||
selectSender,
|
selectSender,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
|
selectTopic,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
|
|
||||||
import { getIsMobile, getIsTablet } from '../../../hooks/useAppLayout';
|
import { getIsMobile, getIsTablet } from '../../../hooks/useAppLayout';
|
||||||
@ -773,8 +774,9 @@ addActionHandler('updatePageTitle', (global, actions, payload): ActionReturnType
|
|||||||
const currentChat = selectChat(global, chatId);
|
const currentChat = selectChat(global, chatId);
|
||||||
if (currentChat) {
|
if (currentChat) {
|
||||||
const title = getChatTitle(langProvider.oldTranslate, currentChat, chatId === currentUserId);
|
const title = getChatTitle(langProvider.oldTranslate, currentChat, chatId === currentUserId);
|
||||||
if (currentChat.isForum && currentChat.topics?.[threadId]) {
|
const topic = selectTopic(global, chatId, threadId);
|
||||||
setPageTitle(`${title} › ${currentChat.topics[threadId].title}`);
|
if (currentChat.isForum && topic) {
|
||||||
|
setPageTitle(`${title} › ${topic.title}`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -36,10 +36,10 @@ import { addActionHandler, getGlobal } from './index';
|
|||||||
import { INITIAL_GLOBAL_STATE, INITIAL_PERFORMANCE_STATE_MID, INITIAL_PERFORMANCE_STATE_MIN } from './initialState';
|
import { INITIAL_GLOBAL_STATE, INITIAL_PERFORMANCE_STATE_MID, INITIAL_PERFORMANCE_STATE_MIN } from './initialState';
|
||||||
import { clearGlobalForLockScreen } from './reducers';
|
import { clearGlobalForLockScreen } from './reducers';
|
||||||
import {
|
import {
|
||||||
selectChat,
|
|
||||||
selectChatLastMessageId,
|
selectChatLastMessageId,
|
||||||
selectChatMessages,
|
selectChatMessages,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
|
selectTopics,
|
||||||
selectViewportIds,
|
selectViewportIds,
|
||||||
selectVisibleUsers,
|
selectVisibleUsers,
|
||||||
} from './selectors';
|
} from './selectors';
|
||||||
@ -248,6 +248,9 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) {
|
|||||||
if (!cached.users.commonChatsById) {
|
if (!cached.users.commonChatsById) {
|
||||||
cached.users.commonChatsById = initialState.users.commonChatsById;
|
cached.users.commonChatsById = initialState.users.commonChatsById;
|
||||||
}
|
}
|
||||||
|
if (!cached.chats.topicsInfoById) {
|
||||||
|
cached.chats.topicsInfoById = initialState.chats.topicsInfoById;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateCache(force?: boolean) {
|
function updateCache(force?: boolean) {
|
||||||
@ -446,6 +449,7 @@ function reduceChats<T extends GlobalState>(global: T): GlobalState['chats'] {
|
|||||||
all: pickTruthy(global.chats.lastMessageIds.all || {}, idsToSave),
|
all: pickTruthy(global.chats.lastMessageIds.all || {}, idsToSave),
|
||||||
saved: global.chats.lastMessageIds.saved,
|
saved: global.chats.lastMessageIds.saved,
|
||||||
},
|
},
|
||||||
|
topicsInfoById: pickTruthy(global.chats.topicsInfoById, currentChatIds),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -486,7 +490,6 @@ function reduceMessages<T extends GlobalState>(global: T): GlobalState['messages
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const chat = selectChat(global, chatId);
|
|
||||||
const chatLastMessageId = selectChatLastMessageId(global, chatId);
|
const chatLastMessageId = selectChatLastMessageId(global, chatId);
|
||||||
|
|
||||||
const openedThreadIds = Array.from(openedChatThreadIds[chatId] || []);
|
const openedThreadIds = Array.from(openedChatThreadIds[chatId] || []);
|
||||||
@ -498,8 +501,9 @@ function reduceMessages<T extends GlobalState>(global: T): GlobalState['messages
|
|||||||
const threadsToSave = pickTruthy(current.threadsById, [MAIN_THREAD_ID, ...threadIds]);
|
const threadsToSave = pickTruthy(current.threadsById, [MAIN_THREAD_ID, ...threadIds]);
|
||||||
|
|
||||||
const viewportIdsToSave = unique(Object.values(threadsToSave).flatMap((thread) => thread.lastViewportIds || []));
|
const viewportIdsToSave = unique(Object.values(threadsToSave).flatMap((thread) => thread.lastViewportIds || []));
|
||||||
const topicLastMessageIds = chat?.topics ? Object.values(chat.topics).map(({ lastMessageId }) => lastMessageId)
|
const topics = selectTopics(global, chatId);
|
||||||
: [];
|
const topicLastMessageIds = topics && forumPanelChatIds.includes(chatId)
|
||||||
|
? Object.values(topics).map(({ lastMessageId }) => lastMessageId) : [];
|
||||||
const savedLastMessageIds = chatId === currentUserId && global.chats.lastMessageIds.saved
|
const savedLastMessageIds = chatId === currentUserId && global.chats.lastMessageIds.saved
|
||||||
? Object.values(global.chats.lastMessageIds.saved) : [];
|
? Object.values(global.chats.lastMessageIds.saved) : [];
|
||||||
const lastMessageIdsToSave = [chatLastMessageId].concat(topicLastMessageIds).concat(savedLastMessageIds)
|
const lastMessageIdsToSave = [chatLastMessageId].concat(topicLastMessageIds).concat(savedLastMessageIds)
|
||||||
|
|||||||
@ -145,15 +145,14 @@ export function isUserRightBanned(chat: ApiChat, key: keyof ApiChatBannedRights,
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getCanPostInChat(
|
export function getCanPostInChat(
|
||||||
chat: ApiChat, threadId: ThreadId, isMessageThread?: boolean, chatFullInfo?: ApiChatFullInfo,
|
chat: ApiChat, topic?: ApiTopic, isMessageThread?: boolean, chatFullInfo?: ApiChatFullInfo,
|
||||||
) {
|
) {
|
||||||
if (threadId !== MAIN_THREAD_ID) {
|
if (topic) {
|
||||||
if (chat.isForum) {
|
if (chat.isForum) {
|
||||||
if (chat.isNotJoined) {
|
if (chat.isNotJoined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const topic = chat.topics?.[threadId];
|
|
||||||
if (topic?.isClosed && !topic.isOwner && !getHasAdminRight(chat, 'manageTopics')) {
|
if (topic?.isClosed && !topic.isOwner && !getHasAdminRight(chat, 'manageTopics')) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -264,18 +263,22 @@ export function getMessageSendingRestrictionReason(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getForumComposerPlaceholder(
|
export function getForumComposerPlaceholder(
|
||||||
lang: LangFn, chat?: ApiChat, threadId: ThreadId = MAIN_THREAD_ID, isReplying?: boolean,
|
lang: LangFn,
|
||||||
|
chat?: ApiChat,
|
||||||
|
threadId: ThreadId = MAIN_THREAD_ID,
|
||||||
|
topics?: Record<number, ApiTopic>,
|
||||||
|
isReplying?: boolean,
|
||||||
) {
|
) {
|
||||||
if (!chat?.isForum) {
|
if (!chat?.isForum) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (threadId === MAIN_THREAD_ID) {
|
if (threadId === MAIN_THREAD_ID) {
|
||||||
if (isReplying || (chat.topics && !chat.topics[GENERAL_TOPIC_ID]?.isClosed)) return undefined;
|
if (isReplying || (topics && !topics[GENERAL_TOPIC_ID]?.isClosed)) return undefined;
|
||||||
return lang('lng_forum_replies_only');
|
return lang('lng_forum_replies_only');
|
||||||
}
|
}
|
||||||
|
|
||||||
const topic = chat.topics?.[threadId];
|
const topic = topics?.[Number(threadId)];
|
||||||
if (!topic) {
|
if (!topic) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -110,6 +110,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
|
|||||||
byId: {},
|
byId: {},
|
||||||
fullInfoById: {},
|
fullInfoById: {},
|
||||||
similarChannelsById: {},
|
similarChannelsById: {},
|
||||||
|
topicsInfoById: {},
|
||||||
loadingParameters: {
|
loadingParameters: {
|
||||||
active: {},
|
active: {},
|
||||||
archived: {},
|
archived: {},
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiChat, ApiChatFullInfo, ApiChatMember, ApiTopic,
|
ApiChat, ApiChatFullInfo, ApiChatMember,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { ChatListType, GlobalState } from '../types';
|
import type { ChatListType, GlobalState } from '../types';
|
||||||
|
|
||||||
@ -8,8 +8,7 @@ import { areDeepEqual } from '../../util/areDeepEqual';
|
|||||||
import {
|
import {
|
||||||
areSortedArraysEqual, buildCollectionByKey, omit, pick, unique,
|
areSortedArraysEqual, buildCollectionByKey, omit, pick, unique,
|
||||||
} from '../../util/iteratees';
|
} from '../../util/iteratees';
|
||||||
import { selectChat, selectChatFullInfo } from '../selectors';
|
import { selectChatFullInfo } from '../selectors';
|
||||||
import { updateThread, updateThreadInfo } from './messages';
|
|
||||||
|
|
||||||
const DEFAULT_CHAT_LISTS: ChatListType[] = ['active', 'archived'];
|
const DEFAULT_CHAT_LISTS: ChatListType[] = ['active', 'archived'];
|
||||||
|
|
||||||
@ -423,95 +422,6 @@ export function addChatMembers<T extends GlobalState>(global: T, chat: ApiChat,
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateListedTopicIds<T extends GlobalState>(
|
|
||||||
global: T, chatId: string, topicIds: number[],
|
|
||||||
): T {
|
|
||||||
return updateChat(global, chatId, {
|
|
||||||
listedTopicIds: unique([
|
|
||||||
...(global.chats.byId[chatId]?.listedTopicIds || []),
|
|
||||||
...topicIds,
|
|
||||||
]),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTopics<T extends GlobalState>(
|
|
||||||
global: T, chatId: string, topicsCount: number, topics: ApiTopic[],
|
|
||||||
): T {
|
|
||||||
const chat = selectChat(global, chatId);
|
|
||||||
|
|
||||||
const newTopics = buildCollectionByKey(topics, 'id');
|
|
||||||
|
|
||||||
global = updateChat(global, chatId, {
|
|
||||||
topics: {
|
|
||||||
...chat?.topics,
|
|
||||||
...newTopics,
|
|
||||||
},
|
|
||||||
topicsCount,
|
|
||||||
});
|
|
||||||
|
|
||||||
topics.forEach((topic) => {
|
|
||||||
global = updateThread(global, chatId, topic.id, {
|
|
||||||
firstMessageId: topic.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
global = updateThreadInfo(global, chatId, topic.id, {
|
|
||||||
lastMessageId: topic.lastMessageId,
|
|
||||||
threadId: topic.id,
|
|
||||||
chatId,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return global;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function updateTopic<T extends GlobalState>(
|
|
||||||
global: T, chatId: string, topicId: number, update: Partial<ApiTopic>,
|
|
||||||
): T {
|
|
||||||
const chat = selectChat(global, chatId);
|
|
||||||
|
|
||||||
if (!chat) return global;
|
|
||||||
|
|
||||||
const topic = chat?.topics?.[topicId];
|
|
||||||
|
|
||||||
const updatedTopic = {
|
|
||||||
...topic,
|
|
||||||
...update,
|
|
||||||
} as ApiTopic;
|
|
||||||
|
|
||||||
if (!updatedTopic.id) return global;
|
|
||||||
|
|
||||||
global = updateChat(global, chatId, {
|
|
||||||
topics: {
|
|
||||||
...(chat.topics || {}),
|
|
||||||
[topicId]: updatedTopic,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
global = updateThread(global, chatId, updatedTopic.id, {
|
|
||||||
firstMessageId: updatedTopic.id,
|
|
||||||
});
|
|
||||||
|
|
||||||
global = updateThreadInfo(global, chatId, updatedTopic.id, {
|
|
||||||
lastMessageId: updatedTopic.lastMessageId,
|
|
||||||
threadId: updatedTopic.id,
|
|
||||||
chatId,
|
|
||||||
});
|
|
||||||
|
|
||||||
return global;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function deleteTopic<T extends GlobalState>(
|
|
||||||
global: T, chatId: string, topicId: number,
|
|
||||||
) {
|
|
||||||
const chat = selectChat(global, chatId);
|
|
||||||
const topics = chat?.topics || {};
|
|
||||||
global = updateChat(global, chatId, {
|
|
||||||
topics: omit(topics, [topicId]),
|
|
||||||
});
|
|
||||||
|
|
||||||
return global;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addSimilarChannels<T extends GlobalState>(
|
export function addSimilarChannels<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
|
|||||||
@ -14,3 +14,4 @@ export * from './stories';
|
|||||||
export * from './translations';
|
export * from './translations';
|
||||||
export * from './peers';
|
export * from './peers';
|
||||||
export * from './password';
|
export * from './password';
|
||||||
|
export * from './topics';
|
||||||
|
|||||||
@ -21,7 +21,6 @@ import {
|
|||||||
mergeIdRanges, orderHistoryIds, orderPinnedIds,
|
mergeIdRanges, orderHistoryIds, orderPinnedIds,
|
||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
import {
|
import {
|
||||||
selectChat,
|
|
||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
selectChatMessages,
|
selectChatMessages,
|
||||||
selectChatScheduledMessages,
|
selectChatScheduledMessages,
|
||||||
@ -43,10 +42,7 @@ import { removeIdFromSearchResults } from './middleSearch';
|
|||||||
import { updateTabState } from './tabs';
|
import { updateTabState } from './tabs';
|
||||||
import { clearMessageTranslation } from './translations';
|
import { clearMessageTranslation } from './translations';
|
||||||
|
|
||||||
type MessageStoreSections = {
|
type MessageStoreSections = GlobalState['messages']['byChatId'][string];
|
||||||
byId: Record<number, ApiMessage>;
|
|
||||||
threadsById: Record<number, Thread>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function updateCurrentMessageList<T extends GlobalState>(
|
export function updateCurrentMessageList<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
@ -128,7 +124,7 @@ export function updateThread<T extends GlobalState>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateMessageStore<T extends GlobalState>(
|
export function updateMessageStore<T extends GlobalState>(
|
||||||
global: T, chatId: string, update: Partial<MessageStoreSections>,
|
global: T, chatId: string, update: Partial<MessageStoreSections>,
|
||||||
): T {
|
): T {
|
||||||
const current = global.messages.byChatId[chatId] || { byId: {}, threadsById: {} };
|
const current = global.messages.byChatId[chatId] || { byId: {}, threadsById: {} };
|
||||||
@ -823,32 +819,6 @@ export function updateThreadUnreadFromForwardedMessage<T extends GlobalState>(
|
|||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateTopicLastMessageId<T extends GlobalState>(
|
|
||||||
global: T, chatId: string, threadId: ThreadId, lastMessageId: number,
|
|
||||||
) {
|
|
||||||
const chat = selectChat(global, chatId);
|
|
||||||
if (!chat?.topics?.[threadId]) return global;
|
|
||||||
return {
|
|
||||||
...global,
|
|
||||||
chats: {
|
|
||||||
...global.chats,
|
|
||||||
byId: {
|
|
||||||
...global.chats.byId,
|
|
||||||
[chatId]: {
|
|
||||||
...chat,
|
|
||||||
topics: {
|
|
||||||
...chat.topics,
|
|
||||||
[threadId]: {
|
|
||||||
...chat.topics[threadId],
|
|
||||||
lastMessageId,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function addActiveMediaDownload<T extends GlobalState>(
|
export function addActiveMediaDownload<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
mediaHash: string,
|
mediaHash: string,
|
||||||
|
|||||||
138
src/global/reducers/topics.ts
Normal file
138
src/global/reducers/topics.ts
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
import type { ApiTopic } from '../../api/types';
|
||||||
|
import type { GlobalState, TopicsInfo } from '../types';
|
||||||
|
|
||||||
|
import { buildCollectionByKey, omit, unique } from '../../util/iteratees';
|
||||||
|
import {
|
||||||
|
selectChat, selectTopic, selectTopics,
|
||||||
|
selectTopicsInfo,
|
||||||
|
} from '../selectors';
|
||||||
|
import { updateThread, updateThreadInfo } from './messages';
|
||||||
|
|
||||||
|
function updateTopicsStore<T extends GlobalState>(
|
||||||
|
global: T, chatId: string, update: Partial<TopicsInfo>,
|
||||||
|
) {
|
||||||
|
const info = global.chats.topicsInfoById[chatId] || {};
|
||||||
|
|
||||||
|
global = {
|
||||||
|
...global,
|
||||||
|
chats: {
|
||||||
|
...global.chats,
|
||||||
|
topicsInfoById: {
|
||||||
|
...global.chats.topicsInfoById,
|
||||||
|
[chatId]: {
|
||||||
|
...info,
|
||||||
|
...update,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return global;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateListedTopicIds<T extends GlobalState>(
|
||||||
|
global: T, chatId: string, topicIds: number[],
|
||||||
|
): T {
|
||||||
|
const listedIds = selectTopicsInfo(global, chatId)?.listedTopicIds || [];
|
||||||
|
return updateTopicsStore(global, chatId, {
|
||||||
|
listedTopicIds: unique([
|
||||||
|
...listedIds,
|
||||||
|
...topicIds,
|
||||||
|
]),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateTopics<T extends GlobalState>(
|
||||||
|
global: T, chatId: string, topicsCount: number, topics: ApiTopic[],
|
||||||
|
): T {
|
||||||
|
const oldTopics = selectTopics(global, chatId);
|
||||||
|
const newTopics = buildCollectionByKey(topics, 'id');
|
||||||
|
|
||||||
|
global = updateTopicsStore(global, chatId, {
|
||||||
|
topicsById: {
|
||||||
|
...oldTopics,
|
||||||
|
...newTopics,
|
||||||
|
},
|
||||||
|
totalCount: topicsCount,
|
||||||
|
});
|
||||||
|
|
||||||
|
topics.forEach((topic) => {
|
||||||
|
global = updateThread(global, chatId, topic.id, {
|
||||||
|
firstMessageId: topic.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
global = updateThreadInfo(global, chatId, topic.id, {
|
||||||
|
lastMessageId: topic.lastMessageId,
|
||||||
|
threadId: topic.id,
|
||||||
|
chatId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return global;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateTopic<T extends GlobalState>(
|
||||||
|
global: T, chatId: string, topicId: number, update: Partial<ApiTopic>,
|
||||||
|
): T {
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
|
||||||
|
if (!chat) return global;
|
||||||
|
|
||||||
|
const topic = selectTopic(global, chatId, topicId);
|
||||||
|
const oldTopics = selectTopics(global, chatId);
|
||||||
|
|
||||||
|
const updatedTopic = {
|
||||||
|
...topic,
|
||||||
|
...update,
|
||||||
|
} as ApiTopic;
|
||||||
|
|
||||||
|
if (!updatedTopic.id) return global;
|
||||||
|
|
||||||
|
global = updateTopicsStore(global, chatId, {
|
||||||
|
topicsById: {
|
||||||
|
...oldTopics,
|
||||||
|
[topicId]: updatedTopic,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
global = updateThread(global, chatId, updatedTopic.id, {
|
||||||
|
firstMessageId: updatedTopic.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
global = updateThreadInfo(global, chatId, updatedTopic.id, {
|
||||||
|
lastMessageId: updatedTopic.lastMessageId,
|
||||||
|
threadId: updatedTopic.id,
|
||||||
|
chatId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return global;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteTopic<T extends GlobalState>(
|
||||||
|
global: T, chatId: string, topicId: number,
|
||||||
|
) {
|
||||||
|
const topics = selectTopics(global, chatId);
|
||||||
|
if (!topics) return global;
|
||||||
|
|
||||||
|
global = updateTopicsStore(global, chatId, {
|
||||||
|
topicsById: omit(topics, [topicId]),
|
||||||
|
});
|
||||||
|
|
||||||
|
return global;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateTopicLastMessageId<T extends GlobalState>(
|
||||||
|
global: T, chatId: string, threadId: number, lastMessageId: number,
|
||||||
|
) {
|
||||||
|
return updateTopic(global, chatId, threadId, {
|
||||||
|
lastMessageId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function replacePinnedTopicIds<T extends GlobalState>(
|
||||||
|
global: T, chatId: string, pinnedTopicIds: number[],
|
||||||
|
) {
|
||||||
|
return updateTopicsStore(global, chatId, {
|
||||||
|
orderedPinnedTopicIds: pinnedTopicIds,
|
||||||
|
});
|
||||||
|
}
|
||||||
@ -12,3 +12,4 @@ export * from './statistics';
|
|||||||
export * from './stories';
|
export * from './stories';
|
||||||
export * from './tabs';
|
export * from './tabs';
|
||||||
export * from './peers';
|
export * from './peers';
|
||||||
|
export * from './topics';
|
||||||
|
|||||||
@ -70,6 +70,7 @@ import { selectPeer } from './peers';
|
|||||||
import { selectPeerStory } from './stories';
|
import { selectPeerStory } from './stories';
|
||||||
import { selectIsStickerFavorite } from './symbols';
|
import { selectIsStickerFavorite } from './symbols';
|
||||||
import { selectTabState } from './tabs';
|
import { selectTabState } from './tabs';
|
||||||
|
import { selectTopic } from './topics';
|
||||||
import {
|
import {
|
||||||
selectBot, selectIsCurrentUserPremium, selectUser, selectUserStatus,
|
selectBot, selectIsCurrentUserPremium, selectUser, selectUserStatus,
|
||||||
} from './users';
|
} from './users';
|
||||||
@ -483,17 +484,21 @@ export function selectForwardedSender<T extends GlobalState>(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectTopicFromMessage<T extends GlobalState>(global: T, message: ApiMessage) {
|
||||||
|
const { chatId } = message;
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
if (!chat?.isForum) return undefined;
|
||||||
|
|
||||||
|
const threadId = selectThreadIdFromMessage(global, message);
|
||||||
|
return selectTopic(global, chatId, threadId);
|
||||||
|
}
|
||||||
|
|
||||||
const MAX_MESSAGES_TO_DELETE_OWNER_TOPIC = 10;
|
const MAX_MESSAGES_TO_DELETE_OWNER_TOPIC = 10;
|
||||||
export function selectCanDeleteOwnerTopic<T extends GlobalState>(global: T, chatId: string, topicId: number) {
|
export function selectCanDeleteOwnerTopic<T extends GlobalState>(global: T, chatId: string, topicId: number) {
|
||||||
const chat = selectChat(global, chatId);
|
const topic = selectTopic(global, chatId, topicId);
|
||||||
if (!chat) {
|
if (topic && !topic.isOwner) return false;
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chat.topics?.[topicId] && !chat.topics?.[topicId].isOwner) return false;
|
|
||||||
|
|
||||||
const thread = global.messages.byChatId[chatId]?.threadsById[topicId];
|
|
||||||
|
|
||||||
|
const thread = selectThread(global, chatId, topicId);
|
||||||
if (!thread) return false;
|
if (!thread) return false;
|
||||||
|
|
||||||
const { listedIds } = thread;
|
const { listedIds } = thread;
|
||||||
@ -576,15 +581,6 @@ export function selectThreadIdFromMessage<T extends GlobalState>(global: T, mess
|
|||||||
return replyToTopId || replyToMsgId || GENERAL_TOPIC_ID;
|
return replyToTopId || replyToMsgId || GENERAL_TOPIC_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectTopicFromMessage<T extends GlobalState>(global: T, message: ApiMessage) {
|
|
||||||
const { chatId } = message;
|
|
||||||
const chat = selectChat(global, chatId);
|
|
||||||
if (!chat?.isForum) return undefined;
|
|
||||||
|
|
||||||
const threadId = selectThreadIdFromMessage(global, message);
|
|
||||||
return chat.topics?.[threadId];
|
|
||||||
}
|
|
||||||
|
|
||||||
export function selectCanReplyToMessage<T extends GlobalState>(global: T, message: ApiMessage, threadId: ThreadId) {
|
export function selectCanReplyToMessage<T extends GlobalState>(global: T, message: ApiMessage, threadId: ThreadId) {
|
||||||
const chat = selectChat(global, message.chatId);
|
const chat = selectChat(global, message.chatId);
|
||||||
if (!chat || chat.isRestricted || chat.isForbidden) return false;
|
if (!chat || chat.isRestricted || chat.isForbidden) return false;
|
||||||
@ -597,7 +593,8 @@ export function selectCanReplyToMessage<T extends GlobalState>(global: T, messag
|
|||||||
const threadInfo = selectThreadInfo(global, message.chatId, threadId);
|
const threadInfo = selectThreadInfo(global, message.chatId, threadId);
|
||||||
const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
|
const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
|
||||||
const chatFullInfo = selectChatFullInfo(global, chat.id);
|
const chatFullInfo = selectChatFullInfo(global, chat.id);
|
||||||
const canPostInChat = getCanPostInChat(chat, threadId, isMessageThread, chatFullInfo);
|
const topic = selectTopic(global, chat.id, threadId);
|
||||||
|
const canPostInChat = getCanPostInChat(chat, topic, isMessageThread, chatFullInfo);
|
||||||
if (!canPostInChat) return false;
|
if (!canPostInChat) return false;
|
||||||
|
|
||||||
const messageTopic = selectTopicFromMessage(global, message);
|
const messageTopic = selectTopicFromMessage(global, message);
|
||||||
|
|||||||
14
src/global/selectors/topics.ts
Normal file
14
src/global/selectors/topics.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import type { ThreadId } from '../../types';
|
||||||
|
import type { GlobalState, TopicsInfo } from '../types';
|
||||||
|
|
||||||
|
export function selectTopicsInfo<T extends GlobalState>(global: T, chatId: string): TopicsInfo | undefined {
|
||||||
|
return global.chats.topicsInfoById[chatId];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectTopics<T extends GlobalState>(global: T, chatId: string) {
|
||||||
|
return selectTopicsInfo(global, chatId)?.topicsById;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function selectTopic<T extends GlobalState>(global: T, chatId: string, threadId: ThreadId) {
|
||||||
|
return selectTopicsInfo(global, chatId)?.topicsById?.[threadId];
|
||||||
|
}
|
||||||
@ -74,6 +74,7 @@ import type {
|
|||||||
ApiThemeParameters,
|
ApiThemeParameters,
|
||||||
ApiThreadInfo,
|
ApiThreadInfo,
|
||||||
ApiTimezone,
|
ApiTimezone,
|
||||||
|
ApiTopic,
|
||||||
ApiTranscription,
|
ApiTranscription,
|
||||||
ApiTypeStoryView,
|
ApiTypeStoryView,
|
||||||
ApiTypingStatus,
|
ApiTypingStatus,
|
||||||
@ -213,6 +214,13 @@ export interface ServiceNotification {
|
|||||||
isDeleted?: boolean;
|
isDeleted?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TopicsInfo {
|
||||||
|
totalCount: number;
|
||||||
|
topicsById: Record<ThreadId, ApiTopic>;
|
||||||
|
listedTopicIds?: number[];
|
||||||
|
orderedPinnedTopicIds?: number[];
|
||||||
|
}
|
||||||
|
|
||||||
export type ApiLimitType =
|
export type ApiLimitType =
|
||||||
| 'uploadMaxFileparts'
|
| 'uploadMaxFileparts'
|
||||||
| 'stickersFaved'
|
| 'stickersFaved'
|
||||||
@ -968,6 +976,7 @@ export type GlobalState = {
|
|||||||
all?: Record<string, number>;
|
all?: Record<string, number>;
|
||||||
saved?: Record<string, number>;
|
saved?: Record<string, number>;
|
||||||
};
|
};
|
||||||
|
topicsInfoById: Record<string, TopicsInfo>;
|
||||||
loadingParameters: Record<ChatListType, {
|
loadingParameters: Record<ChatListType, {
|
||||||
nextOffsetId?: number;
|
nextOffsetId?: number;
|
||||||
nextOffsetPeerId?: string;
|
nextOffsetPeerId?: string;
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import {
|
|||||||
selectNotifyExceptions,
|
selectNotifyExceptions,
|
||||||
selectNotifySettings,
|
selectNotifySettings,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
|
selectTopics,
|
||||||
} from '../global/selectors';
|
} from '../global/selectors';
|
||||||
import arePropsShallowEqual from './arePropsShallowEqual';
|
import arePropsShallowEqual from './arePropsShallowEqual';
|
||||||
import { createCallbackManager } from './callbacks';
|
import { createCallbackManager } from './callbacks';
|
||||||
@ -74,6 +75,7 @@ let prevGlobal: {
|
|||||||
isSavedFolderFullyLoaded?: boolean;
|
isSavedFolderFullyLoaded?: boolean;
|
||||||
lastAllMessageIds?: GlobalState['chats']['lastMessageIds']['all'];
|
lastAllMessageIds?: GlobalState['chats']['lastMessageIds']['all'];
|
||||||
lastSavedMessageIds?: GlobalState['chats']['lastMessageIds']['saved'];
|
lastSavedMessageIds?: GlobalState['chats']['lastMessageIds']['saved'];
|
||||||
|
topicsInfoById: GlobalState['chats']['topicsInfoById'];
|
||||||
chatsById: Record<string, ApiChat>;
|
chatsById: Record<string, ApiChat>;
|
||||||
foldersById: Record<string, ApiChatFolder>;
|
foldersById: Record<string, ApiChatFolder>;
|
||||||
usersById: Record<string, ApiUser>;
|
usersById: Record<string, ApiUser>;
|
||||||
@ -215,6 +217,7 @@ function updateFolderManager(global: GlobalState) {
|
|||||||
const areChatsChanged = global.chats.byId !== prevGlobal.chatsById;
|
const areChatsChanged = global.chats.byId !== prevGlobal.chatsById;
|
||||||
const areSavedLastMessageIdsChanged = global.chats.lastMessageIds.saved !== prevGlobal.lastSavedMessageIds;
|
const areSavedLastMessageIdsChanged = global.chats.lastMessageIds.saved !== prevGlobal.lastSavedMessageIds;
|
||||||
const areAllLastMessageIdsChanged = global.chats.lastMessageIds.all !== prevGlobal.lastAllMessageIds;
|
const areAllLastMessageIdsChanged = global.chats.lastMessageIds.all !== prevGlobal.lastAllMessageIds;
|
||||||
|
const areTopicsChanged = global.chats.topicsInfoById !== prevGlobal.topicsInfoById;
|
||||||
const areUsersChanged = global.users.byId !== prevGlobal.usersById;
|
const areUsersChanged = global.users.byId !== prevGlobal.usersById;
|
||||||
const areNotifySettingsChanged = selectNotifySettings(global) !== prevGlobal.notifySettings;
|
const areNotifySettingsChanged = selectNotifySettings(global) !== prevGlobal.notifySettings;
|
||||||
const areNotifyExceptionsChanged = selectNotifyExceptions(global) !== prevGlobal.notifyExceptions;
|
const areNotifyExceptionsChanged = selectNotifyExceptions(global) !== prevGlobal.notifyExceptions;
|
||||||
@ -229,7 +232,7 @@ function updateFolderManager(global: GlobalState) {
|
|||||||
|
|
||||||
if (!(
|
if (!(
|
||||||
isAllFolderChanged || isArchivedFolderChanged || isSavedFolderChanged || areFoldersChanged
|
isAllFolderChanged || isArchivedFolderChanged || isSavedFolderChanged || areFoldersChanged
|
||||||
|| areChatsChanged || areUsersChanged || areNotifySettingsChanged || areNotifyExceptionsChanged
|
|| areChatsChanged || areUsersChanged || areTopicsChanged || areNotifySettingsChanged || areNotifyExceptionsChanged
|
||||||
|| areSavedLastMessageIdsChanged || areAllLastMessageIdsChanged
|
|| areSavedLastMessageIdsChanged || areAllLastMessageIdsChanged
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
@ -515,8 +518,9 @@ function buildChatSummary<T extends GlobalState>(
|
|||||||
const {
|
const {
|
||||||
id, type, isRestricted, isNotJoined, migratedTo, folderId,
|
id, type, isRestricted, isNotJoined, migratedTo, folderId,
|
||||||
unreadCount: chatUnreadCount, unreadMentionsCount: chatUnreadMentionsCount, hasUnreadMark,
|
unreadCount: chatUnreadCount, unreadMentionsCount: chatUnreadMentionsCount, hasUnreadMark,
|
||||||
isForum, topics,
|
isForum,
|
||||||
} = chat;
|
} = chat;
|
||||||
|
const topics = selectTopics(global, chat.id);
|
||||||
|
|
||||||
const { unreadCount, unreadMentionsCount } = isForum
|
const { unreadCount, unreadMentionsCount } = isForum
|
||||||
? Object.values(topics || {}).reduce((acc, topic) => {
|
? Object.values(topics || {}).reduce((acc, topic) => {
|
||||||
@ -805,6 +809,7 @@ function buildInitials() {
|
|||||||
foldersById: {},
|
foldersById: {},
|
||||||
chatsById: {},
|
chatsById: {},
|
||||||
usersById: {},
|
usersById: {},
|
||||||
|
topicsInfoById: {},
|
||||||
notifySettings: {} as NotifySettings,
|
notifySettings: {} as NotifySettings,
|
||||||
notifyExceptions: {},
|
notifyExceptions: {},
|
||||||
},
|
},
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user