Support local changelogs; Fix marking read for service notifications
This commit is contained in:
parent
aa2617a2fd
commit
691c0c4263
10
.eslintrc
10
.eslintrc
@ -21,7 +21,10 @@
|
|||||||
"error",
|
"error",
|
||||||
120
|
120
|
||||||
],
|
],
|
||||||
"array-bracket-newline": [2, "consistent"],
|
"array-bracket-newline": [
|
||||||
|
2,
|
||||||
|
"consistent"
|
||||||
|
],
|
||||||
"no-null/no-null": 2,
|
"no-null/no-null": 2,
|
||||||
"no-console": "error",
|
"no-console": "error",
|
||||||
"semi": "error",
|
"semi": "error",
|
||||||
@ -68,5 +71,8 @@
|
|||||||
},
|
},
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"project": "./tsconfig.json"
|
"project": "./tsconfig.json"
|
||||||
}
|
},
|
||||||
|
"ignorePatterns": [
|
||||||
|
"webpack.config.js"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,9 @@ import {
|
|||||||
ApiChatAdminRights,
|
ApiChatAdminRights,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { DEBUG, ARCHIVED_FOLDER_ID, MEMBERS_LOAD_SLICE } from '../../../config';
|
import {
|
||||||
|
DEBUG, ARCHIVED_FOLDER_ID, MEMBERS_LOAD_SLICE, SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
|
} from '../../../config';
|
||||||
import { invokeRequest, uploadFile } from './client';
|
import { invokeRequest, uploadFile } from './client';
|
||||||
import {
|
import {
|
||||||
buildApiChatFromDialog,
|
buildApiChatFromDialog,
|
||||||
@ -52,12 +54,14 @@ export async function fetchChats({
|
|||||||
archived,
|
archived,
|
||||||
withPinned,
|
withPinned,
|
||||||
serverTimeOffset,
|
serverTimeOffset,
|
||||||
|
lastLocalServiceMessage,
|
||||||
}: {
|
}: {
|
||||||
limit: number;
|
limit: number;
|
||||||
offsetDate?: number;
|
offsetDate?: number;
|
||||||
archived?: boolean;
|
archived?: boolean;
|
||||||
withPinned?: boolean;
|
withPinned?: boolean;
|
||||||
serverTimeOffset: number;
|
serverTimeOffset: number;
|
||||||
|
lastLocalServiceMessage?: ApiMessage;
|
||||||
}) {
|
}) {
|
||||||
const result = await invokeRequest(new GramJs.messages.GetDialogs({
|
const result = await invokeRequest(new GramJs.messages.GetDialogs({
|
||||||
offsetPeer: new GramJs.InputPeerEmpty(),
|
offsetPeer: new GramJs.InputPeerEmpty(),
|
||||||
@ -111,7 +115,17 @@ export async function fetchChats({
|
|||||||
|
|
||||||
const peerEntity = peersByKey[getPeerKey(dialog.peer)];
|
const peerEntity = peersByKey[getPeerKey(dialog.peer)];
|
||||||
const chat = buildApiChatFromDialog(dialog, peerEntity, serverTimeOffset);
|
const chat = buildApiChatFromDialog(dialog, peerEntity, serverTimeOffset);
|
||||||
chat.lastMessage = lastMessagesByChatId[chat.id];
|
|
||||||
|
if (
|
||||||
|
chat.id === SERVICE_NOTIFICATIONS_USER_ID
|
||||||
|
&& lastLocalServiceMessage
|
||||||
|
&& (!lastMessagesByChatId[chat.id] || lastLocalServiceMessage.date > lastMessagesByChatId[chat.id].date)
|
||||||
|
) {
|
||||||
|
chat.lastMessage = lastLocalServiceMessage;
|
||||||
|
} else {
|
||||||
|
chat.lastMessage = lastMessagesByChatId[chat.id];
|
||||||
|
}
|
||||||
|
|
||||||
chat.isListed = true;
|
chat.isListed = true;
|
||||||
chats.push(chat);
|
chats.push(chat);
|
||||||
|
|
||||||
@ -232,8 +246,10 @@ export async function fetchChat({
|
|||||||
export async function requestChatUpdate({
|
export async function requestChatUpdate({
|
||||||
chat,
|
chat,
|
||||||
serverTimeOffset,
|
serverTimeOffset,
|
||||||
|
lastLocalMessage,
|
||||||
|
noLastMessage,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat; serverTimeOffset: number;
|
chat: ApiChat; serverTimeOffset: number; lastLocalMessage?: ApiMessage; noLastMessage?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { id, accessHash } = chat;
|
const { id, accessHash } = chat;
|
||||||
|
|
||||||
@ -260,14 +276,23 @@ export async function requestChatUpdate({
|
|||||||
|
|
||||||
updateLocalDb(result);
|
updateLocalDb(result);
|
||||||
|
|
||||||
const lastMessage = buildApiMessage(result.messages[0]);
|
let lastMessage: ApiMessage | undefined;
|
||||||
|
if (!noLastMessage) {
|
||||||
|
const lastRemoteMessage = buildApiMessage(result.messages[0]);
|
||||||
|
|
||||||
|
if (lastLocalMessage && (!lastRemoteMessage || (lastLocalMessage.date > lastRemoteMessage.date))) {
|
||||||
|
lastMessage = lastLocalMessage;
|
||||||
|
} else {
|
||||||
|
lastMessage = lastRemoteMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onUpdate({
|
onUpdate({
|
||||||
'@type': 'updateChat',
|
'@type': 'updateChat',
|
||||||
id,
|
id,
|
||||||
chat: {
|
chat: {
|
||||||
...buildApiChatFromDialog(dialog, peerEntity, serverTimeOffset),
|
...buildApiChatFromDialog(dialog, peerEntity, serverTimeOffset),
|
||||||
lastMessage,
|
...(lastMessage && { lastMessage }),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -442,6 +467,7 @@ export async function updateChatMutedState({
|
|||||||
void requestChatUpdate({
|
void requestChatUpdate({
|
||||||
chat,
|
chat,
|
||||||
serverTimeOffset,
|
serverTimeOffset,
|
||||||
|
noLastMessage: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -688,7 +688,7 @@ export async function markMessageListRead({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (threadId === MAIN_THREAD_ID) {
|
if (threadId === MAIN_THREAD_ID) {
|
||||||
void requestChatUpdate({ chat, serverTimeOffset });
|
void requestChatUpdate({ chat, serverTimeOffset, noLastMessage: true });
|
||||||
} else {
|
} else {
|
||||||
void requestThreadInfoUpdate({ chat, threadId });
|
void requestThreadInfoUpdate({ chat, threadId });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -82,7 +82,6 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
|||||||
|| update instanceof GramJs.UpdateNewChannelMessage
|
|| update instanceof GramJs.UpdateNewChannelMessage
|
||||||
|| update instanceof GramJs.UpdateShortChatMessage
|
|| update instanceof GramJs.UpdateShortChatMessage
|
||||||
|| update instanceof GramJs.UpdateShortMessage
|
|| update instanceof GramJs.UpdateShortMessage
|
||||||
|| update instanceof GramJs.UpdateServiceNotification
|
|
||||||
) {
|
) {
|
||||||
let message: ApiMessage | undefined;
|
let message: ApiMessage | undefined;
|
||||||
let shouldForceReply: boolean | undefined;
|
let shouldForceReply: boolean | undefined;
|
||||||
@ -91,13 +90,6 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
|||||||
message = buildApiMessageFromShortChat(update);
|
message = buildApiMessageFromShortChat(update);
|
||||||
} else if (update instanceof GramJs.UpdateShortMessage) {
|
} else if (update instanceof GramJs.UpdateShortMessage) {
|
||||||
message = buildApiMessageFromShort(update);
|
message = buildApiMessageFromShort(update);
|
||||||
} else if (update instanceof GramJs.UpdateServiceNotification) {
|
|
||||||
const currentDate = Date.now() / 1000 + serverTimeOffset;
|
|
||||||
message = buildApiMessageFromNotification(update, currentDate);
|
|
||||||
|
|
||||||
if (isMessageWithMedia(update)) {
|
|
||||||
addMessageToLocalDb(buildMessageFromUpdate(message.id, message.chatId, update));
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// TODO Remove if proven not reproducing
|
// TODO Remove if proven not reproducing
|
||||||
if (update.message instanceof GramJs.MessageEmpty) {
|
if (update.message instanceof GramJs.MessageEmpty) {
|
||||||
@ -319,6 +311,18 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) {
|
|||||||
});
|
});
|
||||||
}, DELETE_MISSING_CHANNEL_MESSAGE_DELAY);
|
}, DELETE_MISSING_CHANNEL_MESSAGE_DELAY);
|
||||||
}
|
}
|
||||||
|
} else if (update instanceof GramJs.UpdateServiceNotification) {
|
||||||
|
const currentDate = Date.now() / 1000 + serverTimeOffset;
|
||||||
|
const message = buildApiMessageFromNotification(update, currentDate);
|
||||||
|
|
||||||
|
if (isMessageWithMedia(update)) {
|
||||||
|
addMessageToLocalDb(buildMessageFromUpdate(message.id, message.chatId, update));
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdate({
|
||||||
|
'@type': 'updateServiceNotification',
|
||||||
|
message,
|
||||||
|
});
|
||||||
} else if ((
|
} else if ((
|
||||||
originRequest instanceof GramJs.messages.SendMessage
|
originRequest instanceof GramJs.messages.SendMessage
|
||||||
|| originRequest instanceof GramJs.messages.SendMedia
|
|| originRequest instanceof GramJs.messages.SendMedia
|
||||||
|
|||||||
@ -239,6 +239,11 @@ export type ApiUpdateMessagePollVote = {
|
|||||||
options: string[];
|
options: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiUpdateServiceNotification = {
|
||||||
|
'@type': 'updateServiceNotification';
|
||||||
|
message: ApiMessage;
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiUpdateDeleteMessages = {
|
export type ApiUpdateDeleteMessages = {
|
||||||
'@type': 'deleteMessages';
|
'@type': 'deleteMessages';
|
||||||
ids: number[];
|
ids: number[];
|
||||||
@ -381,7 +386,7 @@ export type ApiUpdate = (
|
|||||||
ApiUpdateChatListType | ApiUpdateChatFolder | ApiUpdateChatFoldersOrder | ApiUpdateRecommendedChatFolders |
|
ApiUpdateChatListType | ApiUpdateChatFolder | ApiUpdateChatFoldersOrder | ApiUpdateRecommendedChatFolders |
|
||||||
ApiUpdateNewMessage | ApiUpdateMessage | ApiUpdateThreadInfo | ApiUpdateCommonBoxMessages | ApiUpdateChannelMessages |
|
ApiUpdateNewMessage | ApiUpdateMessage | ApiUpdateThreadInfo | ApiUpdateCommonBoxMessages | ApiUpdateChannelMessages |
|
||||||
ApiUpdateDeleteMessages | ApiUpdateMessagePoll | ApiUpdateMessagePollVote | ApiUpdateDeleteHistory |
|
ApiUpdateDeleteMessages | ApiUpdateMessagePoll | ApiUpdateMessagePollVote | ApiUpdateDeleteHistory |
|
||||||
ApiUpdateMessageSendSucceeded | ApiUpdateMessageSendFailed |
|
ApiUpdateMessageSendSucceeded | ApiUpdateMessageSendFailed | ApiUpdateServiceNotification |
|
||||||
ApiDeleteUser | ApiUpdateUser | ApiUpdateUserStatus | ApiUpdateUserFullInfo | ApiUpdateDeleteProfilePhotos |
|
ApiDeleteUser | ApiUpdateUser | ApiUpdateUserStatus | ApiUpdateUserFullInfo | ApiUpdateDeleteProfilePhotos |
|
||||||
ApiUpdateAvatar | ApiUpdateMessageImage | ApiUpdateDraftMessage |
|
ApiUpdateAvatar | ApiUpdateMessageImage | ApiUpdateDraftMessage |
|
||||||
ApiUpdateError | ApiUpdateResetContacts |
|
ApiUpdateError | ApiUpdateResetContacts |
|
||||||
|
|||||||
@ -18,6 +18,7 @@ import {
|
|||||||
selectIsForwardModalOpen,
|
selectIsForwardModalOpen,
|
||||||
selectIsMediaViewerOpen,
|
selectIsMediaViewerOpen,
|
||||||
selectIsRightColumnShown,
|
selectIsRightColumnShown,
|
||||||
|
selectIsServiceChatReady,
|
||||||
} from '../../modules/selectors';
|
} from '../../modules/selectors';
|
||||||
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
import { dispatchHeavyAnimationEvent } from '../../hooks/useHeavyAnimationCheck';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
@ -62,11 +63,12 @@ type StateProps = {
|
|||||||
shouldSkipHistoryAnimations?: boolean;
|
shouldSkipHistoryAnimations?: boolean;
|
||||||
language?: LangCode;
|
language?: LangCode;
|
||||||
openedStickerSetShortName?: string;
|
openedStickerSetShortName?: string;
|
||||||
|
isServiceChatReady?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type DispatchProps = Pick<GlobalActions, (
|
type DispatchProps = Pick<GlobalActions, (
|
||||||
'loadAnimatedEmojis' | 'loadNotificationSettings' | 'loadNotificationExceptions' | 'updateIsOnline' |
|
'loadAnimatedEmojis' | 'loadNotificationSettings' | 'loadNotificationExceptions' | 'updateIsOnline' |
|
||||||
'loadTopInlineBots' | 'loadEmojiKeywords' | 'openStickerSetShortName' | 'loadCountryList'
|
'loadTopInlineBots' | 'loadEmojiKeywords' | 'openStickerSetShortName' | 'loadCountryList' | 'checkVersionNotification'
|
||||||
)>;
|
)>;
|
||||||
|
|
||||||
const NOTIFICATION_INTERVAL = 1000;
|
const NOTIFICATION_INTERVAL = 1000;
|
||||||
@ -92,6 +94,7 @@ const Main: FC<StateProps & DispatchProps> = ({
|
|||||||
shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations,
|
||||||
language,
|
language,
|
||||||
openedStickerSetShortName,
|
openedStickerSetShortName,
|
||||||
|
isServiceChatReady,
|
||||||
loadAnimatedEmojis,
|
loadAnimatedEmojis,
|
||||||
loadNotificationSettings,
|
loadNotificationSettings,
|
||||||
loadNotificationExceptions,
|
loadNotificationExceptions,
|
||||||
@ -100,6 +103,7 @@ const Main: FC<StateProps & DispatchProps> = ({
|
|||||||
loadEmojiKeywords,
|
loadEmojiKeywords,
|
||||||
loadCountryList,
|
loadCountryList,
|
||||||
openStickerSetShortName,
|
openStickerSetShortName,
|
||||||
|
checkVersionNotification,
|
||||||
}) => {
|
}) => {
|
||||||
if (DEBUG && !DEBUG_isLogged) {
|
if (DEBUG && !DEBUG_isLogged) {
|
||||||
DEBUG_isLogged = true;
|
DEBUG_isLogged = true;
|
||||||
@ -128,6 +132,12 @@ const Main: FC<StateProps & DispatchProps> = ({
|
|||||||
loadTopInlineBots, loadEmojiKeywords, loadCountryList, language,
|
loadTopInlineBots, loadEmojiKeywords, loadCountryList, language,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (lastSyncTime && isServiceChatReady) {
|
||||||
|
checkVersionNotification();
|
||||||
|
}
|
||||||
|
}, [lastSyncTime, isServiceChatReady, checkVersionNotification]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (lastSyncTime && LOCATION_HASH.startsWith('#?tgaddr=')) {
|
if (lastSyncTime && LOCATION_HASH.startsWith('#?tgaddr=')) {
|
||||||
processDeepLink(decodeURIComponent(LOCATION_HASH.substr('#?tgaddr='.length)));
|
processDeepLink(decodeURIComponent(LOCATION_HASH.substr('#?tgaddr='.length)));
|
||||||
@ -302,10 +312,11 @@ export default memo(withGlobal(
|
|||||||
shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations: global.shouldSkipHistoryAnimations,
|
||||||
language: global.settings.byKey.language,
|
language: global.settings.byKey.language,
|
||||||
openedStickerSetShortName: global.openedStickerSetShortName,
|
openedStickerSetShortName: global.openedStickerSetShortName,
|
||||||
|
isServiceChatReady: selectIsServiceChatReady(global),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
(setGlobal, actions): DispatchProps => pick(actions, [
|
(setGlobal, actions): DispatchProps => pick(actions, [
|
||||||
'loadAnimatedEmojis', 'loadNotificationSettings', 'loadNotificationExceptions', 'updateIsOnline',
|
'loadAnimatedEmojis', 'loadNotificationSettings', 'loadNotificationExceptions', 'updateIsOnline',
|
||||||
'loadTopInlineBots', 'loadEmojiKeywords', 'openStickerSetShortName', 'loadCountryList',
|
'loadTopInlineBots', 'loadEmojiKeywords', 'openStickerSetShortName', 'loadCountryList', 'checkVersionNotification',
|
||||||
]),
|
]),
|
||||||
)(Main));
|
)(Main));
|
||||||
|
|||||||
@ -396,7 +396,7 @@ const MessageList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isResized = prevContainerHeight !== undefined && prevContainerHeight !== containerHeight;
|
const isResized = prevContainerHeight !== undefined && prevContainerHeight !== containerHeight;
|
||||||
const anchor = anchorIdRef.current && container.querySelector(`#${anchorIdRef.current}`);
|
const anchor = anchorIdRef.current && document.getElementById(anchorIdRef.current);
|
||||||
const unreadDivider = (
|
const unreadDivider = (
|
||||||
!anchor
|
!anchor
|
||||||
&& memoUnreadDividerBeforeIdRef.current
|
&& memoUnreadDividerBeforeIdRef.current
|
||||||
|
|||||||
@ -297,17 +297,13 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
const senderPeer = forwardInfo ? originSender : sender;
|
const senderPeer = forwardInfo ? originSender : sender;
|
||||||
|
|
||||||
const selectMessage = useCallback((e?: React.MouseEvent<HTMLDivElement, MouseEvent>, groupedId?: string) => {
|
const selectMessage = useCallback((e?: React.MouseEvent<HTMLDivElement, MouseEvent>, groupedId?: string) => {
|
||||||
if (isLocal) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleMessageSelection({
|
toggleMessageSelection({
|
||||||
messageId,
|
messageId,
|
||||||
groupedId,
|
groupedId,
|
||||||
...(e?.shiftKey && { withShift: true }),
|
...(e?.shiftKey && { withShift: true }),
|
||||||
...(isAlbum && { childMessageIds: album!.messages.map(({ id }) => id) }),
|
...(isAlbum && { childMessageIds: album!.messages.map(({ id }) => id) }),
|
||||||
});
|
});
|
||||||
}, [isLocal, toggleMessageSelection, messageId, isAlbum, album]);
|
}, [toggleMessageSelection, messageId, isAlbum, album]);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
handleMouseDown,
|
handleMouseDown,
|
||||||
@ -320,7 +316,6 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
selectMessage,
|
selectMessage,
|
||||||
ref,
|
ref,
|
||||||
messageId,
|
messageId,
|
||||||
isLocal,
|
|
||||||
isAlbum,
|
isAlbum,
|
||||||
Boolean(isInSelectMode),
|
Boolean(isInSelectMode),
|
||||||
Boolean(canReply),
|
Boolean(canReply),
|
||||||
@ -705,12 +700,12 @@ const Message: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
data-last-message-id={album ? album.messages[album.messages.length - 1].id : undefined}
|
data-last-message-id={album ? album.messages[album.messages.length - 1].id : undefined}
|
||||||
data-has-unread-mention={message.hasUnreadMention}
|
data-has-unread-mention={message.hasUnreadMention}
|
||||||
/>
|
/>
|
||||||
{!isLocal && !isInDocumentGroup && (
|
{!isInDocumentGroup && (
|
||||||
<div className="message-select-control">
|
<div className="message-select-control">
|
||||||
{isSelected && <i className="icon-select" />}
|
{isSelected && <i className="icon-select" />}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{!isLocal && isLastInDocumentGroup && (
|
{isLastInDocumentGroup && (
|
||||||
<div
|
<div
|
||||||
className={buildClassName('message-select-control group-select', isGroupSelected && 'is-selected')}
|
className={buildClassName('message-select-control group-select', isGroupSelected && 'is-selected')}
|
||||||
onClick={handleDocumentGroupSelectAll}
|
onClick={handleDocumentGroupSelectAll}
|
||||||
|
|||||||
@ -15,7 +15,6 @@ export default function useOuterHandlers(
|
|||||||
selectMessage: (e?: React.MouseEvent<HTMLDivElement, MouseEvent>, groupedId?: string) => void,
|
selectMessage: (e?: React.MouseEvent<HTMLDivElement, MouseEvent>, groupedId?: string) => void,
|
||||||
containerRef: RefObject<HTMLDivElement>,
|
containerRef: RefObject<HTMLDivElement>,
|
||||||
messageId: number,
|
messageId: number,
|
||||||
isLocal: boolean,
|
|
||||||
isAlbum: boolean,
|
isAlbum: boolean,
|
||||||
isInSelectMode: boolean,
|
isInSelectMode: boolean,
|
||||||
canReply: boolean,
|
canReply: boolean,
|
||||||
@ -28,14 +27,11 @@ export default function useOuterHandlers(
|
|||||||
|
|
||||||
function handleMouseDown(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
function handleMouseDown(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
||||||
preventMessageInputBlur(e);
|
preventMessageInputBlur(e);
|
||||||
|
handleBeforeContextMenu(e);
|
||||||
if (!isLocal) {
|
|
||||||
handleBeforeContextMenu(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleClick(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
function handleClick(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
||||||
if (isInSelectMode && !isLocal) {
|
if (isInSelectMode) {
|
||||||
selectMessage(e);
|
selectMessage(e);
|
||||||
} else if (IS_ANDROID) {
|
} else if (IS_ANDROID) {
|
||||||
const target = e.target as HTMLDivElement;
|
const target = e.target as HTMLDivElement;
|
||||||
@ -111,7 +107,7 @@ export default function useOuterHandlers(
|
|||||||
return {
|
return {
|
||||||
handleMouseDown: !isInSelectMode ? handleMouseDown : undefined,
|
handleMouseDown: !isInSelectMode ? handleMouseDown : undefined,
|
||||||
handleClick,
|
handleClick,
|
||||||
handleContextMenu: !isInSelectMode && !isLocal ? handleContextMenu : undefined,
|
handleContextMenu: !isInSelectMode ? handleContextMenu : undefined,
|
||||||
handleDoubleClick: !isInSelectMode ? handleContainerDoubleClick : undefined,
|
handleDoubleClick: !isInSelectMode ? handleContainerDoubleClick : undefined,
|
||||||
handleContentDoubleClick: !IS_TOUCH_ENV ? stopPropagation : undefined,
|
handleContentDoubleClick: !IS_TOUCH_ENV ? stopPropagation : undefined,
|
||||||
isSwiped,
|
isSwiped,
|
||||||
|
|||||||
@ -127,6 +127,10 @@ function readCache(initialState: GlobalState): GlobalState {
|
|||||||
byChatId: {},
|
byChatId: {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!cached.serviceNotifications) {
|
||||||
|
cached.serviceNotifications = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const newState = {
|
const newState = {
|
||||||
@ -171,6 +175,7 @@ function updateCache() {
|
|||||||
'push',
|
'push',
|
||||||
'shouldShowContextMenuHint',
|
'shouldShowContextMenuHint',
|
||||||
'leftColumnWidth',
|
'leftColumnWidth',
|
||||||
|
'serviceNotifications',
|
||||||
]),
|
]),
|
||||||
isChatInfoShown: reduceShowChatInfo(global),
|
isChatInfoShown: reduceShowChatInfo(global),
|
||||||
users: reduceUsers(global),
|
users: reduceUsers(global),
|
||||||
|
|||||||
@ -167,4 +167,6 @@ export const INITIAL_STATE: GlobalState = {
|
|||||||
activeDownloads: {
|
activeDownloads: {
|
||||||
byChatId: {},
|
byChatId: {},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
serviceNotifications: [],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -72,6 +72,12 @@ export interface Thread {
|
|||||||
replyStack?: number[];
|
replyStack?: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ServiceNotification {
|
||||||
|
id: number;
|
||||||
|
message: ApiMessage;
|
||||||
|
version?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type GlobalState = {
|
export type GlobalState = {
|
||||||
isChatInfoShown: boolean;
|
isChatInfoShown: boolean;
|
||||||
isLeftColumnShown: boolean;
|
isLeftColumnShown: boolean;
|
||||||
@ -443,6 +449,8 @@ export type GlobalState = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
shouldShowContextMenuHint?: boolean;
|
shouldShowContextMenuHint?: boolean;
|
||||||
|
|
||||||
|
serviceNotifications: ServiceNotification[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ActionTypes = (
|
export type ActionTypes = (
|
||||||
@ -521,7 +529,7 @@ export type ActionTypes = (
|
|||||||
// misc
|
// misc
|
||||||
'openMediaViewer' | 'closeMediaViewer' | 'openAudioPlayer' | 'closeAudioPlayer' | 'openPollModal' | 'closePollModal' |
|
'openMediaViewer' | 'closeMediaViewer' | 'openAudioPlayer' | 'closeAudioPlayer' | 'openPollModal' | 'closePollModal' |
|
||||||
'loadWebPagePreview' | 'clearWebPagePreview' | 'loadWallpapers' | 'uploadWallpaper' | 'setDeviceToken' |
|
'loadWebPagePreview' | 'clearWebPagePreview' | 'loadWallpapers' | 'uploadWallpaper' | 'setDeviceToken' |
|
||||||
'deleteDeviceToken' |
|
'deleteDeviceToken' | 'checkVersionNotification' | 'createServiceNotification' |
|
||||||
// payment
|
// payment
|
||||||
'openPaymentModal' | 'closePaymentModal' | 'addPaymentError' |
|
'openPaymentModal' | 'closePaymentModal' | 'addPaymentError' |
|
||||||
'validateRequestedInfo' | 'setPaymentStep' | 'sendPaymentForm' | 'getPaymentForm' | 'getReceipt' |
|
'validateRequestedInfo' | 'setPaymentStep' | 'sendPaymentForm' | 'getPaymentForm' | 'getReceipt' |
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import {
|
|||||||
RE_TME_INVITE_LINK,
|
RE_TME_INVITE_LINK,
|
||||||
RE_TME_LINK,
|
RE_TME_LINK,
|
||||||
TIPS_USERNAME,
|
TIPS_USERNAME,
|
||||||
LOCALIZED_TIPS, RE_TG_LINK, RE_TME_ADDSTICKERS_LINK,
|
LOCALIZED_TIPS, RE_TG_LINK, RE_TME_ADDSTICKERS_LINK, SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
} from '../../../config';
|
} from '../../../config';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import {
|
import {
|
||||||
@ -38,7 +38,7 @@ import {
|
|||||||
selectChatByUsername,
|
selectChatByUsername,
|
||||||
selectThreadTopMessageId,
|
selectThreadTopMessageId,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectThreadInfo, selectCurrentChat,
|
selectThreadInfo, selectCurrentChat, selectLastServiceNotification,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||||
import { debounce, pause, throttle } from '../../../util/schedulers';
|
import { debounce, pause, throttle } from '../../../util/schedulers';
|
||||||
@ -255,6 +255,9 @@ addReducer('requestChatUpdate', (global, actions, payload) => {
|
|||||||
void callApi('requestChatUpdate', {
|
void callApi('requestChatUpdate', {
|
||||||
chat,
|
chat,
|
||||||
serverTimeOffset,
|
serverTimeOffset,
|
||||||
|
...(chatId === SERVICE_NOTIFICATIONS_USER_ID && {
|
||||||
|
lastLocalMessage: selectLastServiceNotification(global)?.message,
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -964,12 +967,15 @@ addReducer('deleteChatMember', (global, actions, payload) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function loadChats(listType: 'active' | 'archived', offsetId?: number, offsetDate?: number) {
|
async function loadChats(listType: 'active' | 'archived', offsetId?: number, offsetDate?: number) {
|
||||||
|
let global = getGlobal();
|
||||||
|
|
||||||
const result = await callApi('fetchChats', {
|
const result = await callApi('fetchChats', {
|
||||||
limit: CHAT_LIST_LOAD_SLICE,
|
limit: CHAT_LIST_LOAD_SLICE,
|
||||||
offsetDate,
|
offsetDate,
|
||||||
archived: listType === 'archived',
|
archived: listType === 'archived',
|
||||||
withPinned: getGlobal().chats.orderedPinnedIds[listType] === undefined,
|
withPinned: global.chats.orderedPinnedIds[listType] === undefined,
|
||||||
serverTimeOffset: getGlobal().serverTimeOffset,
|
serverTimeOffset: global.serverTimeOffset,
|
||||||
|
lastLocalServiceMessage: selectLastServiceNotification(global)?.message,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
@ -982,7 +988,7 @@ async function loadChats(listType: 'active' | 'archived', offsetId?: number, off
|
|||||||
chatIds.shift();
|
chatIds.shift();
|
||||||
}
|
}
|
||||||
|
|
||||||
let global = getGlobal();
|
global = getGlobal();
|
||||||
|
|
||||||
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||||
global = updateChats(global, buildCollectionByKey(result.chats, 'id'));
|
global = updateChats(global, buildCollectionByKey(result.chats, 'id'));
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import {
|
|||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import { LoadMoreDirection } from '../../../types';
|
import { LoadMoreDirection } from '../../../types';
|
||||||
|
|
||||||
import { MAX_MEDIA_FILES_FOR_ALBUM, MESSAGE_LIST_SLICE } from '../../../config';
|
import { MAX_MEDIA_FILES_FOR_ALBUM, MESSAGE_LIST_SLICE, SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
|
||||||
import { callApi, cancelApiProgress } from '../../../api/gramjs';
|
import { callApi, cancelApiProgress } from '../../../api/gramjs';
|
||||||
import { areSortedArraysIntersecting, buildCollectionByKey, split } from '../../../util/iteratees';
|
import { areSortedArraysIntersecting, buildCollectionByKey, split } from '../../../util/iteratees';
|
||||||
import {
|
import {
|
||||||
@ -663,11 +663,15 @@ async function loadViewportMessages(
|
|||||||
messages, users, chats, threadInfos,
|
messages, users, chats, threadInfos,
|
||||||
} = result;
|
} = result;
|
||||||
|
|
||||||
const byId = buildCollectionByKey(messages, 'id');
|
|
||||||
const ids = Object.keys(byId).map(Number);
|
|
||||||
|
|
||||||
let global = getGlobal();
|
let global = getGlobal();
|
||||||
|
|
||||||
|
const localMessages = chatId === SERVICE_NOTIFICATIONS_USER_ID
|
||||||
|
? global.serviceNotifications.map(({ message }) => message)
|
||||||
|
: [];
|
||||||
|
const allMessages = ([] as ApiMessage[]).concat(messages, localMessages);
|
||||||
|
const byId = buildCollectionByKey(allMessages, 'id');
|
||||||
|
const ids = Object.keys(byId).map(Number);
|
||||||
|
|
||||||
global = addChatMessagesById(global, chatId, byId);
|
global = addChatMessagesById(global, chatId, byId);
|
||||||
global = isOutlying
|
global = isOutlying
|
||||||
? updateOutlyingIds(global, chatId, threadId, ids)
|
? updateOutlyingIds(global, chatId, threadId, ids)
|
||||||
|
|||||||
@ -3,12 +3,12 @@ import {
|
|||||||
} from '../../../lib/teact/teactn';
|
} from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ApiChat, ApiFormattedText, ApiUser, MAIN_THREAD_ID,
|
ApiChat, ApiFormattedText, ApiMessage, ApiUser, MAIN_THREAD_ID,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import { GlobalActions } from '../../../global/types';
|
import { GlobalActions } from '../../../global/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CHAT_LIST_LOAD_SLICE, DEBUG, MESSAGE_LIST_SLICE,
|
CHAT_LIST_LOAD_SLICE, DEBUG, MESSAGE_LIST_SLICE, SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
} from '../../../config';
|
} from '../../../config';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||||
@ -22,6 +22,9 @@ import {
|
|||||||
updateChatListSecondaryInfo,
|
updateChatListSecondaryInfo,
|
||||||
updateThreadInfos,
|
updateThreadInfos,
|
||||||
replaceThreadParam,
|
replaceThreadParam,
|
||||||
|
updateListedIds,
|
||||||
|
safeReplaceViewportIds,
|
||||||
|
addChatMessagesById,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import {
|
import {
|
||||||
selectUser,
|
selectUser,
|
||||||
@ -31,6 +34,7 @@ import {
|
|||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
selectCountNotMutedUnread,
|
selectCountNotMutedUnread,
|
||||||
|
selectLastServiceNotification,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import { isChatPrivate } from '../../helpers';
|
import { isChatPrivate } from '../../helpers';
|
||||||
|
|
||||||
@ -91,16 +95,20 @@ async function afterSync(actions: GlobalActions) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function loadAndReplaceChats() {
|
async function loadAndReplaceChats() {
|
||||||
|
let global = getGlobal();
|
||||||
|
|
||||||
const result = await callApi('fetchChats', {
|
const result = await callApi('fetchChats', {
|
||||||
limit: CHAT_LIST_LOAD_SLICE,
|
limit: CHAT_LIST_LOAD_SLICE,
|
||||||
withPinned: true,
|
withPinned: true,
|
||||||
serverTimeOffset: getGlobal().serverTimeOffset,
|
serverTimeOffset: global.serverTimeOffset,
|
||||||
|
lastLocalServiceMessage: selectLastServiceNotification(global)?.message,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
let global = getGlobal();
|
global = getGlobal();
|
||||||
|
|
||||||
const { recentlyFoundChatIds } = global.globalSearch;
|
const { recentlyFoundChatIds } = global.globalSearch;
|
||||||
const { userIds: contactIds } = global.contactList || {};
|
const { userIds: contactIds } = global.contactList || {};
|
||||||
@ -211,29 +219,25 @@ async function loadAndReplaceMessages(savedUsers?: ApiUser[]) {
|
|||||||
|
|
||||||
if (result && newCurrentChatId === currentChatId) {
|
if (result && newCurrentChatId === currentChatId) {
|
||||||
const currentMessageListInfo = global.messages.byChatId[currentChatId];
|
const currentMessageListInfo = global.messages.byChatId[currentChatId];
|
||||||
const byId = buildCollectionByKey(result.messages, 'id');
|
const localMessages = currentChatId === SERVICE_NOTIFICATIONS_USER_ID
|
||||||
|
? global.serviceNotifications.map(({ message }) => message)
|
||||||
|
: [];
|
||||||
|
const allMessages = ([] as ApiMessage[]).concat(result.messages, localMessages);
|
||||||
|
const byId = buildCollectionByKey(allMessages, 'id');
|
||||||
const listedIds = Object.keys(byId).map(Number);
|
const listedIds = Object.keys(byId).map(Number);
|
||||||
|
|
||||||
global = {
|
global = {
|
||||||
...global,
|
...global,
|
||||||
messages: {
|
messages: {
|
||||||
...global.messages,
|
...global.messages,
|
||||||
byChatId: {
|
byChatId: {},
|
||||||
[currentChatId]: {
|
|
||||||
byId,
|
|
||||||
threadsById: {
|
|
||||||
[MAIN_THREAD_ID]: {
|
|
||||||
...(currentMessageListInfo?.threadsById[MAIN_THREAD_ID]),
|
|
||||||
listedIds,
|
|
||||||
viewportIds: listedIds,
|
|
||||||
outlyingIds: undefined,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
global = addChatMessagesById(global, currentChatId, byId);
|
||||||
|
global = updateListedIds(global, currentChatId, MAIN_THREAD_ID, listedIds);
|
||||||
|
global = safeReplaceViewportIds(global, currentChatId, MAIN_THREAD_ID, listedIds);
|
||||||
|
|
||||||
if (currentThreadId && threadInfo && threadInfo.originChannelId) {
|
if (currentThreadId && threadInfo && threadInfo.originChannelId) {
|
||||||
const { originChannelId } = threadInfo;
|
const { originChannelId } = threadInfo;
|
||||||
const currentMessageListInfoOrigin = global.messages.byChatId[originChannelId];
|
const currentMessageListInfoOrigin = global.messages.byChatId[originChannelId];
|
||||||
@ -275,6 +279,7 @@ async function loadAndReplaceMessages(savedUsers?: ApiUser[]) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
global = updateChats(global, buildCollectionByKey(result.chats, 'id'));
|
global = updateChats(global, buildCollectionByKey(result.chats, 'id'));
|
||||||
global = updateThreadInfos(global, currentChatId, result.threadInfos);
|
global = updateThreadInfos(global, currentChatId, result.threadInfos);
|
||||||
|
|
||||||
|
|||||||
@ -38,6 +38,7 @@ import {
|
|||||||
selectFirstUnreadId,
|
selectFirstUnreadId,
|
||||||
selectChat,
|
selectChat,
|
||||||
selectIsChatWithBot,
|
selectIsChatWithBot,
|
||||||
|
selectIsServiceChatReady,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import { getMessageContent, isChatPrivate, isMessageLocal } from '../../helpers';
|
import { getMessageContent, isChatPrivate, isMessageLocal } from '../../helpers';
|
||||||
|
|
||||||
@ -433,6 +434,16 @@ addReducer('apiUpdate', (global, actions, update: ApiUpdate) => {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'updateServiceNotification': {
|
||||||
|
const { message } = update;
|
||||||
|
|
||||||
|
if (selectIsServiceChatReady(global)) {
|
||||||
|
actions.createServiceNotification({ message });
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,14 @@
|
|||||||
import { addReducer, getGlobal, setGlobal } from '../../../lib/teact/teactn';
|
import { addReducer, getGlobal, setGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
import { ApiMessage, MAIN_THREAD_ID } from '../../../api/types';
|
||||||
import { FocusDirection } from '../../../types';
|
import { FocusDirection } from '../../../types';
|
||||||
|
|
||||||
import { ANIMATION_END_DELAY, FAST_SMOOTH_MAX_DURATION } from '../../../config';
|
import {
|
||||||
|
ANIMATION_END_DELAY,
|
||||||
|
APP_VERSION,
|
||||||
|
FAST_SMOOTH_MAX_DURATION,
|
||||||
|
SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
|
} from '../../../config';
|
||||||
import { IS_TOUCH_ENV } from '../../../util/environment';
|
import { IS_TOUCH_ENV } from '../../../util/environment';
|
||||||
import {
|
import {
|
||||||
enterMessageSelectMode,
|
enterMessageSelectMode,
|
||||||
@ -29,10 +34,15 @@ import {
|
|||||||
selectReplyStack,
|
selectReplyStack,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import { findLast } from '../../../util/iteratees';
|
import { findLast } from '../../../util/iteratees';
|
||||||
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
import versionNotification from '../../../versionNotification.txt';
|
||||||
|
|
||||||
const FOCUS_DURATION = 1500;
|
const FOCUS_DURATION = 1500;
|
||||||
const FOCUS_NO_HIGHLIGHT_DURATION = FAST_SMOOTH_MAX_DURATION + ANIMATION_END_DELAY;
|
const FOCUS_NO_HIGHLIGHT_DURATION = FAST_SMOOTH_MAX_DURATION + ANIMATION_END_DELAY;
|
||||||
const POLL_RESULT_OPEN_DELAY_MS = 450;
|
const POLL_RESULT_OPEN_DELAY_MS = 450;
|
||||||
|
const SERVICE_NOTIFICATIONS_MAX_AMOUNT = 1e4;
|
||||||
|
|
||||||
let blurTimeout: number | undefined;
|
let blurTimeout: number | undefined;
|
||||||
|
|
||||||
@ -506,3 +516,65 @@ addReducer('closePollModal', (global) => {
|
|||||||
isPollModalOpen: false,
|
isPollModalOpen: false,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addReducer('checkVersionNotification', (global, actions) => {
|
||||||
|
const currentVersion = APP_VERSION.split('.').slice(0, 2).join('.');
|
||||||
|
const { serviceNotifications } = global;
|
||||||
|
|
||||||
|
if (serviceNotifications.find(({ version }) => version === currentVersion)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message: Omit<ApiMessage, 'id'> = {
|
||||||
|
chatId: SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
|
date: getServerTime(global.serverTimeOffset),
|
||||||
|
content: {
|
||||||
|
text: {
|
||||||
|
text: versionNotification,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
isOutgoing: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
actions.createServiceNotification({
|
||||||
|
message,
|
||||||
|
version: currentVersion,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
addReducer('createServiceNotification', (global, actions, payload) => {
|
||||||
|
const { message, version } = payload;
|
||||||
|
const { serviceNotifications } = global;
|
||||||
|
const serviceChat = selectChat(global, SERVICE_NOTIFICATIONS_USER_ID)!;
|
||||||
|
|
||||||
|
const maxId = Math.max(
|
||||||
|
serviceChat.lastMessage?.id || 0,
|
||||||
|
...serviceNotifications.map(({ id }) => id),
|
||||||
|
);
|
||||||
|
const fractionalPart = (serviceNotifications.length + 1) / SERVICE_NOTIFICATIONS_MAX_AMOUNT;
|
||||||
|
// The fractional ID is made of the largest integer ID and an incremented fractional part
|
||||||
|
const id = Math.floor(maxId) + fractionalPart;
|
||||||
|
|
||||||
|
message.id = id;
|
||||||
|
|
||||||
|
const serviceNotification = {
|
||||||
|
id,
|
||||||
|
message,
|
||||||
|
version,
|
||||||
|
};
|
||||||
|
|
||||||
|
setGlobal({
|
||||||
|
...global,
|
||||||
|
serviceNotifications: [
|
||||||
|
...serviceNotifications,
|
||||||
|
serviceNotification,
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
actions.apiUpdate({
|
||||||
|
'@type': 'newMessage',
|
||||||
|
id: message.id,
|
||||||
|
chatId: message.chatId,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -199,7 +199,7 @@ export function isActionMessage(message: ApiMessage) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isServiceNotificationMessage(message: ApiMessage) {
|
export function isServiceNotificationMessage(message: ApiMessage) {
|
||||||
return message.chatId === SERVICE_NOTIFICATIONS_USER_ID && isMessageLocal(message);
|
return message.chatId === SERVICE_NOTIFICATIONS_USER_ID && Math.round(message.id) !== message.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isAnonymousOwnMessage(message: ApiMessage) {
|
export function isAnonymousOwnMessage(message: ApiMessage) {
|
||||||
|
|||||||
@ -366,14 +366,15 @@ export function safeReplaceViewportIds(
|
|||||||
threadId: number,
|
threadId: number,
|
||||||
newViewportIds: number[],
|
newViewportIds: number[],
|
||||||
): GlobalState {
|
): GlobalState {
|
||||||
const viewportIds = selectViewportIds(global, chatId, threadId) || [];
|
const currentIds = selectViewportIds(global, chatId, threadId) || [];
|
||||||
|
const newIds = orderHistoryIds(newViewportIds);
|
||||||
|
|
||||||
return replaceThreadParam(
|
return replaceThreadParam(
|
||||||
global,
|
global,
|
||||||
chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
'viewportIds',
|
'viewportIds',
|
||||||
areSortedArraysEqual(viewportIds, newViewportIds) ? viewportIds : newViewportIds,
|
areSortedArraysEqual(currentIds, newIds) ? currentIds : newIds,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,9 @@ import {
|
|||||||
getPrivateChatUserId, isChatChannel, isChatPrivate, isHistoryClearMessage, isUserBot, isUserOnline, selectIsChatMuted,
|
getPrivateChatUserId, isChatChannel, isChatPrivate, isHistoryClearMessage, isUserBot, isUserOnline, selectIsChatMuted,
|
||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
import { selectUser } from './users';
|
import { selectUser } from './users';
|
||||||
import { ALL_FOLDER_ID, ARCHIVED_FOLDER_ID, MEMBERS_LOAD_SLICE } from '../../config';
|
import {
|
||||||
|
ALL_FOLDER_ID, ARCHIVED_FOLDER_ID, MEMBERS_LOAD_SLICE, SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
|
} from '../../config';
|
||||||
import { selectNotifyExceptions, selectNotifySettings } from './settings';
|
import { selectNotifyExceptions, selectNotifySettings } from './settings';
|
||||||
|
|
||||||
export function selectChat(global: GlobalState, chatId: number): ApiChat | undefined {
|
export function selectChat(global: GlobalState, chatId: number): ApiChat | undefined {
|
||||||
@ -175,3 +177,7 @@ export function selectCountNotMutedUnread(global: GlobalState) {
|
|||||||
return acc;
|
return acc;
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectIsServiceChatReady(global: GlobalState) {
|
||||||
|
return Boolean(selectChat(global, SERVICE_NOTIFICATIONS_USER_ID));
|
||||||
|
}
|
||||||
|
|||||||
@ -348,18 +348,20 @@ export function selectAllowedMessageActions(global: GlobalState, message: ApiMes
|
|||||||
const isBasicGroup = isChatBasicGroup(chat);
|
const isBasicGroup = isChatBasicGroup(chat);
|
||||||
const isSuperGroup = isChatSuperGroup(chat);
|
const isSuperGroup = isChatSuperGroup(chat);
|
||||||
const isChannel = isChatChannel(chat);
|
const isChannel = isChatChannel(chat);
|
||||||
|
const isLocal = isMessageLocal(message);
|
||||||
const isServiceNotification = isServiceNotificationMessage(message);
|
const isServiceNotification = isServiceNotificationMessage(message);
|
||||||
|
|
||||||
const isOwn = isOwnMessage(message);
|
const isOwn = isOwnMessage(message);
|
||||||
const isAction = isActionMessage(message);
|
const isAction = isActionMessage(message);
|
||||||
const { content } = message;
|
const { content } = message;
|
||||||
|
|
||||||
const canEditMessagesIndefinitely = isChatWithSelf
|
const canEditMessagesIndefinitely = isChatWithSelf
|
||||||
|| (isSuperGroup && getHasAdminRight(chat, 'pinMessages'))
|
|| (isSuperGroup && getHasAdminRight(chat, 'pinMessages'))
|
||||||
|| (isChannel && getHasAdminRight(chat, 'editMessages'));
|
|| (isChannel && getHasAdminRight(chat, 'editMessages'));
|
||||||
const isMessageEditable = (
|
const isMessageEditable = (
|
||||||
(canEditMessagesIndefinitely
|
(
|
||||||
|| getServerTime(global.serverTimeOffset) - message.date < MESSAGE_EDIT_ALLOWED_TIME)
|
canEditMessagesIndefinitely
|
||||||
&& !(
|
|| getServerTime(global.serverTimeOffset) - message.date < MESSAGE_EDIT_ALLOWED_TIME
|
||||||
|
) && !(
|
||||||
content.sticker || content.contact || content.poll || content.action || content.audio
|
content.sticker || content.contact || content.poll || content.action || content.audio
|
||||||
|| (content.video?.isRound)
|
|| (content.video?.isRound)
|
||||||
)
|
)
|
||||||
@ -367,7 +369,7 @@ export function selectAllowedMessageActions(global: GlobalState, message: ApiMes
|
|||||||
&& !message.viaBotId
|
&& !message.viaBotId
|
||||||
);
|
);
|
||||||
|
|
||||||
const canReply = getCanPostInChat(chat, threadId) && !isServiceNotification;
|
const canReply = !isLocal && !isServiceNotification && getCanPostInChat(chat, threadId);
|
||||||
|
|
||||||
const hasPinPermission = isPrivate || (
|
const hasPinPermission = isPrivate || (
|
||||||
chat.isCreator
|
chat.isCreator
|
||||||
@ -375,7 +377,7 @@ export function selectAllowedMessageActions(global: GlobalState, message: ApiMes
|
|||||||
|| getHasAdminRight(chat, 'pinMessages')
|
|| getHasAdminRight(chat, 'pinMessages')
|
||||||
);
|
);
|
||||||
|
|
||||||
let canPin = !isAction && hasPinPermission;
|
let canPin = !isLocal && !isServiceNotification && !isAction && hasPinPermission;
|
||||||
let canUnpin = false;
|
let canUnpin = false;
|
||||||
|
|
||||||
const pinnedMessageIds = selectPinnedIds(global, chat.id);
|
const pinnedMessageIds = selectPinnedIds(global, chat.id);
|
||||||
@ -385,27 +387,29 @@ export function selectAllowedMessageActions(global: GlobalState, message: ApiMes
|
|||||||
canPin = !canUnpin;
|
canPin = !canUnpin;
|
||||||
}
|
}
|
||||||
|
|
||||||
const canDelete = isPrivate
|
const canDelete = !isLocal && !isServiceNotification && (
|
||||||
|
isPrivate
|
||||||
|| isOwn
|
|| isOwn
|
||||||
|| isBasicGroup
|
|| isBasicGroup
|
||||||
|| chat.isCreator
|
|| chat.isCreator
|
||||||
|| getHasAdminRight(chat, 'deleteMessages');
|
|| getHasAdminRight(chat, 'deleteMessages')
|
||||||
|
);
|
||||||
|
|
||||||
const canReport = !isPrivate && !isOwn;
|
const canReport = !isPrivate && !isOwn;
|
||||||
|
|
||||||
const canDeleteForAll = canDelete && !isServiceNotification && (
|
const canDeleteForAll = canDelete && (
|
||||||
(isPrivate && !isChatWithSelf)
|
(isPrivate && !isChatWithSelf)
|
||||||
|| (isBasicGroup && (
|
|| (isBasicGroup && (
|
||||||
isOwn || getHasAdminRight(chat, 'deleteMessages') || chat.isCreator
|
isOwn || getHasAdminRight(chat, 'deleteMessages') || chat.isCreator
|
||||||
))
|
))
|
||||||
);
|
);
|
||||||
|
|
||||||
const canEdit = !isAction && isMessageEditable && (
|
const canEdit = !isLocal && !isAction && isMessageEditable && (
|
||||||
isOwn
|
isOwn
|
||||||
|| (isChannel && (chat.isCreator || getHasAdminRight(chat, 'editMessages')))
|
|| (isChannel && (chat.isCreator || getHasAdminRight(chat, 'editMessages')))
|
||||||
);
|
);
|
||||||
|
|
||||||
const canForward = !isAction && !isServiceNotification;
|
const canForward = !isLocal && !isServiceNotification && !isAction;
|
||||||
|
|
||||||
const hasSticker = Boolean(message.content.sticker);
|
const hasSticker = Boolean(message.content.sticker);
|
||||||
const hasFavoriteSticker = hasSticker && selectIsStickerFavorite(global, message.content.sticker!);
|
const hasFavoriteSticker = hasSticker && selectIsStickerFavorite(global, message.content.sticker!);
|
||||||
@ -751,3 +755,10 @@ export function selectShouldAutoPlayMedia(global: GlobalState, message: ApiMessa
|
|||||||
export function selectShouldLoopStickers(global: GlobalState) {
|
export function selectShouldLoopStickers(global: GlobalState) {
|
||||||
return global.settings.byKey.shouldLoopStickers;
|
return global.settings.byKey.shouldLoopStickers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectLastServiceNotification(global: GlobalState) {
|
||||||
|
const { serviceNotifications } = global;
|
||||||
|
const maxId = Math.max(...serviceNotifications.map(({ id }) => id));
|
||||||
|
|
||||||
|
return serviceNotifications.find(({ id }) => id === maxId);
|
||||||
|
}
|
||||||
|
|||||||
3
src/versionNotification.txt
Normal file
3
src/versionNotification.txt
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Welcome to dev version.
|
||||||
|
|
||||||
|
This is a demo notification.
|
||||||
@ -1,12 +1,15 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const dotenv = require('dotenv');
|
const dotenv = require('dotenv');
|
||||||
|
|
||||||
const { EnvironmentPlugin, ProvidePlugin } = require('webpack');
|
const {
|
||||||
|
EnvironmentPlugin,
|
||||||
|
ProvidePlugin,
|
||||||
|
} = require('webpack');
|
||||||
const HtmlPlugin = require('html-webpack-plugin');
|
const HtmlPlugin = require('html-webpack-plugin');
|
||||||
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
|
||||||
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
|
||||||
const TerserJSPlugin = require('terser-webpack-plugin');
|
const TerserJSPlugin = require('terser-webpack-plugin');
|
||||||
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
|
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
|
||||||
|
|
||||||
dotenv.config();
|
dotenv.config();
|
||||||
|
|
||||||
@ -84,7 +87,7 @@ module.exports = (env = {}, argv = {}) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.tl$/i,
|
test: /\.(txt|tl)$/i,
|
||||||
loader: 'raw-loader',
|
loader: 'raw-loader',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -92,11 +95,11 @@ module.exports = (env = {}, argv = {}) => {
|
|||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.js', '.ts', '.tsx'],
|
extensions: ['.js', '.ts', '.tsx'],
|
||||||
fallback: {
|
fallback: {
|
||||||
path: require.resolve("path-browserify"),
|
path: require.resolve('path-browserify'),
|
||||||
os: require.resolve("os-browserify/browser"),
|
os: require.resolve('os-browserify/browser'),
|
||||||
buffer: require.resolve("buffer/"),
|
buffer: require.resolve('buffer/'),
|
||||||
fs: false,
|
fs: false,
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new HtmlPlugin({
|
new HtmlPlugin({
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user