Message Context Menu: Support "Seen By" (#1564)
This commit is contained in:
parent
e81dd51872
commit
559c1c80dd
@ -22,7 +22,7 @@ export {
|
||||
markMessageListRead, markMessagesRead, requestThreadInfoUpdate, searchMessagesLocal, searchMessagesGlobal,
|
||||
fetchWebPagePreview, editMessage, forwardMessages, loadPollOptionResults, sendPollVote, findFirstMessageIdAfterDate,
|
||||
fetchPinnedMessages, fetchScheduledHistory, sendScheduledMessages, rescheduleMessage, deleteScheduledMessages,
|
||||
reportMessages,
|
||||
reportMessages, fetchSeenBy,
|
||||
} from './messages';
|
||||
|
||||
export {
|
||||
|
||||
@ -1172,3 +1172,12 @@ export async function fetchPinnedMessages({ chat }: { chat: ApiChat }) {
|
||||
chats,
|
||||
};
|
||||
}
|
||||
|
||||
export async function fetchSeenBy({ chat, messageId }: { chat: ApiChat; messageId: number }) {
|
||||
const result = await invokeRequest(new GramJs.messages.GetMessageReadParticipants({
|
||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||
msgId: messageId,
|
||||
}));
|
||||
|
||||
return result ? result.map(String) : undefined;
|
||||
}
|
||||
|
||||
@ -262,6 +262,7 @@ export interface ApiMessage {
|
||||
isScheduled?: boolean;
|
||||
shouldHideKeyboardButtons?: boolean;
|
||||
isFromScheduled?: boolean;
|
||||
seenByUserIds?: string[];
|
||||
}
|
||||
|
||||
export interface ApiThreadInfo {
|
||||
|
||||
@ -11,6 +11,7 @@ export { default as DeleteMessageModal } from '../components/common/DeleteMessag
|
||||
export { default as PinMessageModal } from '../components/common/PinMessageModal';
|
||||
export { default as UnpinAllMessagesModal } from '../components/common/UnpinAllMessagesModal';
|
||||
export { default as MessageSelectToolbar } from '../components/middle/MessageSelectToolbar';
|
||||
export { default as SeenByModal } from '../components/common/SeenByModal';
|
||||
|
||||
export { default as LeftSearch } from '../components/left/search/LeftSearch';
|
||||
export { default as Settings } from '../components/left/settings/Settings';
|
||||
|
||||
15
src/components/common/SeenByModal.async.tsx
Normal file
15
src/components/common/SeenByModal.async.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import React, { FC, memo } from '../../lib/teact/teact';
|
||||
import { OwnProps } from './SeenByModal';
|
||||
import { Bundles } from '../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../hooks/useModuleLoader';
|
||||
|
||||
const SeenByModalAsync: FC<OwnProps> = (props) => {
|
||||
const { isOpen } = props;
|
||||
const SeenByModal = useModuleLoader(Bundles.Extra, 'SeenByModal', !isOpen);
|
||||
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
return SeenByModal ? <SeenByModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default memo(SeenByModalAsync);
|
||||
87
src/components/common/SeenByModal.tsx
Normal file
87
src/components/common/SeenByModal.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import React, { FC, useCallback, memo } from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../lib/teact/teactn';
|
||||
|
||||
import { GlobalActions } from '../../global/types';
|
||||
|
||||
import { pick } from '../../util/iteratees';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import { selectChatMessage } from '../../modules/selectors';
|
||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||
|
||||
import Modal from '../ui/Modal';
|
||||
import Button from '../ui/Button';
|
||||
import PrivateChatInfo from './PrivateChatInfo';
|
||||
import ListItem from '../ui/ListItem';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen: boolean;
|
||||
};
|
||||
|
||||
export type StateProps = {
|
||||
memberIds?: string[];
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, 'openChat' | 'closeSeenByModal'>;
|
||||
|
||||
const CLOSE_ANIMATION_DURATION = 100;
|
||||
|
||||
const SeenByModal: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
isOpen,
|
||||
memberIds,
|
||||
openChat,
|
||||
closeSeenByModal,
|
||||
}) => {
|
||||
const lang = useLang();
|
||||
|
||||
const handleClick = useCallback((userId: string) => {
|
||||
closeSeenByModal();
|
||||
|
||||
setTimeout(() => {
|
||||
openChat({ id: userId });
|
||||
}, CLOSE_ANIMATION_DURATION);
|
||||
}, [closeSeenByModal, openChat]);
|
||||
|
||||
const renderingMemberIds = useCurrentOrPrev(memberIds, true);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onClose={closeSeenByModal}
|
||||
className="narrow"
|
||||
title="Which users read the message"
|
||||
>
|
||||
<div dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
{renderingMemberIds && renderingMemberIds.map((userId) => (
|
||||
<ListItem
|
||||
key={userId}
|
||||
className="chat-item-clickable scroll-item small-icon"
|
||||
onClick={() => handleClick(userId)}
|
||||
>
|
||||
<PrivateChatInfo userId={userId} />
|
||||
</ListItem>
|
||||
))}
|
||||
</div>
|
||||
<Button
|
||||
className="confirm-dialog-button"
|
||||
isText
|
||||
onClick={closeSeenByModal}
|
||||
>
|
||||
{lang('Close')}
|
||||
</Button>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global): StateProps => {
|
||||
const { chatId, messageId } = global.seenByModal || {};
|
||||
if (!chatId || !messageId) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
memberIds: selectChatMessage(global, chatId, messageId)?.seenByUserIds,
|
||||
};
|
||||
},
|
||||
(setGlobal, actions): DispatchProps => pick(actions, ['openChat', 'closeSeenByModal']),
|
||||
)(SeenByModal));
|
||||
@ -64,6 +64,7 @@ import MessageSelectToolbar from './MessageSelectToolbar.async';
|
||||
import UnpinAllMessagesModal from '../common/UnpinAllMessagesModal.async';
|
||||
import PaymentModal from '../payment/PaymentModal.async';
|
||||
import ReceiptModal from '../payment/ReceiptModal.async';
|
||||
import SeenByModal from '../common/SeenByModal.async';
|
||||
|
||||
import './MiddleColumn.scss';
|
||||
|
||||
@ -90,6 +91,7 @@ type StateProps = {
|
||||
isSelectModeActive?: boolean;
|
||||
isPaymentModalOpen?: boolean;
|
||||
isReceiptModalOpen?: boolean;
|
||||
isSeenByModalOpen: boolean;
|
||||
animationLevel?: number;
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
currentTransitionKey: number;
|
||||
@ -134,6 +136,7 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
||||
isSelectModeActive,
|
||||
isPaymentModalOpen,
|
||||
isReceiptModalOpen,
|
||||
isSeenByModalOpen,
|
||||
animationLevel,
|
||||
shouldSkipHistoryAnimations,
|
||||
currentTransitionKey,
|
||||
@ -469,6 +472,7 @@ const MiddleColumn: FC<StateProps & DispatchProps> = ({
|
||||
isOpen={Boolean(isReceiptModalOpen)}
|
||||
onClose={clearReceipt}
|
||||
/>
|
||||
<SeenByModal isOpen={isSeenByModalOpen} />
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
@ -519,6 +523,7 @@ export default memo(withGlobal(
|
||||
isSelectModeActive: selectIsInSelectMode(global),
|
||||
isPaymentModalOpen: global.payment.isPaymentModalOpen,
|
||||
isReceiptModalOpen: Boolean(global.payment.receipt),
|
||||
isSeenByModalOpen: Boolean(global.seenByModal),
|
||||
animationLevel: global.settings.byKey.animationLevel,
|
||||
currentTransitionKey: Math.max(0, global.messages.messageLists.length - 1),
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React, {
|
||||
FC, memo, useCallback, useMemo, useState,
|
||||
FC, memo, useCallback, useEffect, useMemo, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { withGlobal } from '../../../lib/teact/teactn';
|
||||
import { getGlobal, withGlobal } from '../../../lib/teact/teactn';
|
||||
|
||||
import { GlobalActions, MessageListType } from '../../../global/types';
|
||||
import { ApiMessage } from '../../../api/types';
|
||||
@ -9,9 +9,14 @@ import { IAlbum, IAnchorPosition } from '../../../types';
|
||||
import {
|
||||
selectActiveDownloadIds,
|
||||
selectAllowedMessageActions,
|
||||
selectChat,
|
||||
selectCurrentMessageList,
|
||||
} from '../../../modules/selectors';
|
||||
import { isChatGroup, isOwnMessage } from '../../../modules/helpers';
|
||||
import { SEEN_BY_MEMBERS_EXPIRE, SEEN_BY_MEMBERS_CHAT_MAX } from '../../../config';
|
||||
import { pick } from '../../../util/iteratees';
|
||||
import { getDayStartAt } from '../../../util/dateFormat';
|
||||
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||
import useShowTransition from '../../../hooks/useShowTransition';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
|
||||
@ -20,8 +25,6 @@ import ReportMessageModal from '../../common/ReportMessageModal';
|
||||
import PinMessageModal from '../../common/PinMessageModal';
|
||||
import MessageContextMenu from './MessageContextMenu';
|
||||
import CalendarModal from '../../common/CalendarModal';
|
||||
import { getDayStartAt } from '../../../util/dateFormat';
|
||||
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||
|
||||
export type OwnProps = {
|
||||
isOpen: boolean;
|
||||
@ -52,12 +55,14 @@ type StateProps = {
|
||||
canSelect?: boolean;
|
||||
canDownload?: boolean;
|
||||
activeDownloads: number[];
|
||||
canShowSeenBy?: boolean;
|
||||
};
|
||||
|
||||
type DispatchProps = Pick<GlobalActions, (
|
||||
'setReplyingToId' | 'setEditingId' | 'pinMessage' | 'openForwardMenu' |
|
||||
'faveSticker' | 'unfaveSticker' | 'toggleMessageSelection' | 'sendScheduledMessages' | 'rescheduleMessage' |
|
||||
'downloadMessageMedia' | 'cancelMessageMediaDownload'
|
||||
'downloadMessageMedia' | 'cancelMessageMediaDownload' | 'loadSeenBy' |
|
||||
'openSeenByModal'
|
||||
)>;
|
||||
|
||||
const ContextMenuContainer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
@ -86,6 +91,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
canSelect,
|
||||
canDownload,
|
||||
activeDownloads,
|
||||
canShowSeenBy,
|
||||
setReplyingToId,
|
||||
setEditingId,
|
||||
pinMessage,
|
||||
@ -97,6 +103,8 @@ const ContextMenuContainer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
rescheduleMessage,
|
||||
downloadMessageMedia,
|
||||
cancelMessageMediaDownload,
|
||||
loadSeenBy,
|
||||
openSeenByModal,
|
||||
}) => {
|
||||
const { transitionClassNames } = useShowTransition(isOpen, onCloseAnimationEnd, undefined, false);
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(true);
|
||||
@ -105,6 +113,22 @@ const ContextMenuContainer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
const [isPinModalOpen, setIsPinModalOpen] = useState(false);
|
||||
const [isCalendarOpen, openCalendar, closeCalendar] = useFlag();
|
||||
|
||||
useEffect(() => {
|
||||
if (canShowSeenBy && isOpen) {
|
||||
loadSeenBy({ chatId: message.chatId, messageId: message.id });
|
||||
}
|
||||
}, [loadSeenBy, isOpen, message.chatId, message.id, canShowSeenBy]);
|
||||
|
||||
const seenByRecentUsers = useMemo(() => {
|
||||
if (!message.seenByUserIds) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// No need for expensive global updates on users, so we avoid them
|
||||
const usersById = getGlobal().users.byId;
|
||||
return message.seenByUserIds?.slice(0, 3).map((id) => usersById[id]).filter(Boolean);
|
||||
}, [message.seenByUserIds]);
|
||||
|
||||
const isDownloading = album ? album.messages.some((msg) => activeDownloads.includes(msg.id))
|
||||
: activeDownloads.includes(message.id);
|
||||
|
||||
@ -206,6 +230,11 @@ const ContextMenuContainer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
openCalendar();
|
||||
}, [openCalendar]);
|
||||
|
||||
const handleOpenSeenByModal = useCallback(() => {
|
||||
closeMenu();
|
||||
openSeenByModal({ chatId: message.chatId, messageId: message.id });
|
||||
}, [closeMenu, message.chatId, message.id, openSeenByModal]);
|
||||
|
||||
const handleRescheduleMessage = useCallback((date: Date) => {
|
||||
rescheduleMessage({
|
||||
chatId: message.chatId,
|
||||
@ -262,7 +291,9 @@ const ContextMenuContainer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
canCopyLink={canCopyLink}
|
||||
canSelect={canSelect}
|
||||
canDownload={canDownload}
|
||||
canShowSeenBy={canShowSeenBy}
|
||||
isDownloading={isDownloading}
|
||||
seenByRecentUsers={seenByRecentUsers}
|
||||
onReply={handleReply}
|
||||
onEdit={handleEdit}
|
||||
onPin={handlePin}
|
||||
@ -278,6 +309,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||
onClose={closeMenu}
|
||||
onCopyLink={handleCopyLink}
|
||||
onDownload={handleDownloadClick}
|
||||
onShowSeenBy={handleOpenSeenByModal}
|
||||
/>
|
||||
<DeleteMessageModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
@ -314,6 +346,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
(global, { message, messageListType }): StateProps => {
|
||||
const { threadId } = selectCurrentMessageList(global) || {};
|
||||
const activeDownloads = selectActiveDownloadIds(global, message.chatId);
|
||||
const chat = selectChat(global, message.chatId);
|
||||
const {
|
||||
noOptions,
|
||||
canReply,
|
||||
@ -332,6 +365,12 @@ export default memo(withGlobal<OwnProps>(
|
||||
} = (threadId && selectAllowedMessageActions(global, message, threadId)) || {};
|
||||
const isPinned = messageListType === 'pinned';
|
||||
const isScheduled = messageListType === 'scheduled';
|
||||
const canShowSeenBy = Boolean(chat
|
||||
&& isChatGroup(chat)
|
||||
&& isOwnMessage(message)
|
||||
&& chat.membersCount
|
||||
&& chat.membersCount < SEEN_BY_MEMBERS_CHAT_MAX
|
||||
&& message.date > Date.now() / 1000 - SEEN_BY_MEMBERS_EXPIRE);
|
||||
|
||||
return {
|
||||
noOptions,
|
||||
@ -351,6 +390,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
canSelect,
|
||||
canDownload,
|
||||
activeDownloads,
|
||||
canShowSeenBy,
|
||||
};
|
||||
},
|
||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||
@ -365,5 +405,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
'rescheduleMessage',
|
||||
'downloadMessageMedia',
|
||||
'cancelMessageMediaDownload',
|
||||
'loadSeenBy',
|
||||
'openSeenByModal',
|
||||
]),
|
||||
)(ContextMenuContainer));
|
||||
|
||||
@ -12,4 +12,21 @@
|
||||
.backdrop {
|
||||
touch-action: none;
|
||||
}
|
||||
|
||||
.avatars {
|
||||
display: flex;
|
||||
align-self: center;
|
||||
margin-left: auto;
|
||||
padding-left: 1rem;
|
||||
|
||||
.Avatar {
|
||||
border: .0625rem solid var(--color-background);
|
||||
margin-right: 0;
|
||||
box-sizing: content-box;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-left: -0.1875rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import React, {
|
||||
FC, useCallback, useEffect, useRef,
|
||||
FC, memo, useCallback, useEffect, useRef,
|
||||
} from '../../../lib/teact/teact';
|
||||
|
||||
import { ApiMessage } from '../../../api/types';
|
||||
import { ApiMessage, ApiUser } from '../../../api/types';
|
||||
import { IAnchorPosition } from '../../../types';
|
||||
|
||||
import { getMessageCopyOptions } from './helpers/copyOptions';
|
||||
@ -12,6 +12,7 @@ import useLang from '../../../hooks/useLang';
|
||||
|
||||
import Menu from '../../ui/Menu';
|
||||
import MenuItem from '../../ui/MenuItem';
|
||||
import Avatar from '../../common/Avatar';
|
||||
|
||||
import './MessageContextMenu.scss';
|
||||
|
||||
@ -35,6 +36,8 @@ type OwnProps = {
|
||||
canSelect?: boolean;
|
||||
canDownload?: boolean;
|
||||
isDownloading?: boolean;
|
||||
canShowSeenBy?: boolean;
|
||||
seenByRecentUsers?: ApiUser[];
|
||||
onReply: () => void;
|
||||
onEdit: () => void;
|
||||
onPin: () => void;
|
||||
@ -51,6 +54,7 @@ type OwnProps = {
|
||||
onCloseAnimationEnd?: () => void;
|
||||
onCopyLink?: () => void;
|
||||
onDownload?: () => void;
|
||||
onShowSeenBy?: () => void;
|
||||
};
|
||||
|
||||
const SCROLLBAR_WIDTH = 10;
|
||||
@ -75,6 +79,8 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
canSelect,
|
||||
canDownload,
|
||||
isDownloading,
|
||||
canShowSeenBy,
|
||||
seenByRecentUsers,
|
||||
onReply,
|
||||
onEdit,
|
||||
onPin,
|
||||
@ -91,6 +97,7 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
onCloseAnimationEnd,
|
||||
onCopyLink,
|
||||
onDownload,
|
||||
onShowSeenBy,
|
||||
}) => {
|
||||
// eslint-disable-next-line no-null/no-null
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
@ -166,9 +173,24 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
{canForward && <MenuItem icon="forward" onClick={onForward}>{lang('Forward')}</MenuItem>}
|
||||
{canSelect && <MenuItem icon="select" onClick={onSelect}>{lang('Common.Select')}</MenuItem>}
|
||||
{canReport && <MenuItem icon="flag" onClick={onReport}>{lang('lng_context_report_msg')}</MenuItem>}
|
||||
{canShowSeenBy && (
|
||||
<MenuItem icon="group" onClick={onShowSeenBy} disabled={!message.seenByUserIds?.length}>
|
||||
{message.seenByUserIds?.length
|
||||
? lang('Conversation.ContextMenuSeen', message.seenByUserIds.length, 'i')
|
||||
: lang('Conversation.ContextMenuNoViews')}
|
||||
<div className="avatars">
|
||||
{seenByRecentUsers?.map((user) => (
|
||||
<Avatar
|
||||
size="micro"
|
||||
user={user}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</MenuItem>
|
||||
)}
|
||||
{canDelete && <MenuItem destructive icon="delete" onClick={onDelete}>{lang('Delete')}</MenuItem>}
|
||||
</Menu>
|
||||
);
|
||||
};
|
||||
|
||||
export default MessageContextMenu;
|
||||
export default memo(MessageContextMenu);
|
||||
|
||||
@ -124,6 +124,7 @@
|
||||
flex-grow: 1;
|
||||
padding: 1rem;
|
||||
overflow-y: auto;
|
||||
max-height: 90vh;
|
||||
|
||||
b,
|
||||
strong {
|
||||
|
||||
@ -171,6 +171,9 @@ export const LIGHT_THEME_BG_COLOR = '#A2AF8E';
|
||||
export const DARK_THEME_BG_COLOR = '#0F0F0F';
|
||||
export const DARK_THEME_PATTERN_COLOR = '#0a0a0a8c';
|
||||
export const DEFAULT_PATTERN_COLOR = 'rgba(90, 110, 70, 0.6)';
|
||||
// TODO Get values from `getConfig` method once it's available
|
||||
export const SEEN_BY_MEMBERS_CHAT_MAX = 50;
|
||||
export const SEEN_BY_MEMBERS_EXPIRE = 604680; // One week - 2 min
|
||||
|
||||
// Group calls
|
||||
export const GROUP_CALL_VOLUME_MULTIPLIER = 100;
|
||||
|
||||
@ -205,6 +205,11 @@ export type GlobalState = {
|
||||
messageIds: number[];
|
||||
};
|
||||
|
||||
seenByModal?: {
|
||||
chatId: string;
|
||||
messageId: number;
|
||||
};
|
||||
|
||||
fileUploads: {
|
||||
byMessageLocalId: Record<string, {
|
||||
progress: number;
|
||||
@ -476,6 +481,7 @@ export type ActionTypes = (
|
||||
'toggleChatInfo' | 'setIsUiReady' | 'addRecentEmoji' | 'addRecentSticker' | 'toggleLeftColumn' |
|
||||
'toggleSafeLinkModal' | 'openHistoryCalendar' | 'closeHistoryCalendar' | 'disableContextMenuHint' |
|
||||
'setNewChatMembersDialogState' | 'disableHistoryAnimations' | 'setLeftColumnWidth' | 'resetLeftColumnWidth' |
|
||||
'openSeenByModal' | 'closeSeenByModal' |
|
||||
// auth
|
||||
'setAuthPhoneNumber' | 'setAuthCode' | 'setAuthPassword' | 'signUp' | 'returnToAuthPhoneNumber' | 'signOut' |
|
||||
'setAuthRememberMe' | 'clearAuthError' | 'uploadProfilePhoto' | 'goToAuthQrCode' | 'clearCache' |
|
||||
@ -496,7 +502,7 @@ export type ActionTypes = (
|
||||
'openTelegramLink' | 'openChatByUsername' | 'requestThreadInfoUpdate' | 'setScrollOffset' | 'unpinAllMessages' |
|
||||
'setReplyingToId' | 'setEditingId' | 'editLastMessage' | 'saveDraft' | 'clearDraft' | 'loadPinnedMessages' |
|
||||
'toggleMessageWebPage' | 'replyToNextMessage' | 'deleteChatUser' | 'deleteChat' |
|
||||
'reportMessages' | 'focusNextReply' | 'openChatByInvite' |
|
||||
'reportMessages' | 'focusNextReply' | 'openChatByInvite' | 'loadSeenBy' |
|
||||
// downloads
|
||||
'downloadSelectedMessages' | 'downloadMessageMedia' | 'cancelMessageMediaDownload' |
|
||||
// scheduled messages
|
||||
|
||||
@ -1054,6 +1054,7 @@ messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.Disc
|
||||
messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool;
|
||||
messages.unpinAllMessages#f025bc8b peer:InputPeer = messages.AffectedHistory;
|
||||
messages.deleteChat#5bd0ee50 chat_id:long = Bool;
|
||||
messages.getMessageReadParticipants#2c6f97b7 peer:InputPeer msg_id:int = Vector<long>;
|
||||
updates.getState#edd4882a = updates.State;
|
||||
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
|
||||
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
|
||||
|
||||
@ -1055,6 +1055,7 @@ messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.Disc
|
||||
messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool;
|
||||
messages.unpinAllMessages#f025bc8b peer:InputPeer = messages.AffectedHistory;
|
||||
messages.deleteChat#5bd0ee50 chat_id:long = Bool;
|
||||
messages.getMessageReadParticipants#2c6f97b7 peer:InputPeer msg_id:int = Vector<long>;
|
||||
updates.getState#edd4882a = updates.State;
|
||||
updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
|
||||
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
|
||||
|
||||
@ -932,6 +932,25 @@ addReducer('loadPinnedMessages', (global, actions, payload) => {
|
||||
void loadPinnedMessages(chat);
|
||||
});
|
||||
|
||||
addReducer('loadSeenBy', (global, actions, payload) => {
|
||||
const { chatId, messageId } = payload;
|
||||
const chat = selectChat(global, chatId);
|
||||
if (!chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
const result = await callApi('fetchSeenBy', { chat, messageId });
|
||||
if (!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
setGlobal(updateChatMessage(getGlobal(), chatId, messageId, {
|
||||
seenByUserIds: result,
|
||||
}));
|
||||
})();
|
||||
});
|
||||
|
||||
async function loadPinnedMessages(chat: ApiChat) {
|
||||
const result = await callApi('fetchPinnedMessages', { chat });
|
||||
if (!result) {
|
||||
|
||||
@ -641,3 +641,19 @@ addReducer('createServiceNotification', (global, actions, payload) => {
|
||||
message,
|
||||
});
|
||||
});
|
||||
|
||||
addReducer('openSeenByModal', (global, actions, payload) => {
|
||||
const { chatId, messageId } = payload!;
|
||||
|
||||
return {
|
||||
...global,
|
||||
seenByModal: { chatId, messageId },
|
||||
};
|
||||
});
|
||||
|
||||
addReducer('closeSeenByModal', (global) => {
|
||||
return {
|
||||
...global,
|
||||
seenByModal: undefined,
|
||||
};
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user