Scheduled Messages: Support repeated mode (#6503)
This commit is contained in:
parent
d81fe3ec96
commit
9d03d14af7
@ -47,7 +47,7 @@ import { getEmojiOnlyCountForMessage } from '../../../global/helpers/getEmojiOnl
|
|||||||
import { addTimestampEntities } from '../../../util/dates/timestamp';
|
import { addTimestampEntities } from '../../../util/dates/timestamp';
|
||||||
import { omitUndefined, pick } from '../../../util/iteratees';
|
import { omitUndefined, pick } from '../../../util/iteratees';
|
||||||
import { toJSNumber } from '../../../util/numbers';
|
import { toJSNumber } from '../../../util/numbers';
|
||||||
import { getServerTime, getServerTimeOffset } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
import { interpolateArray } from '../../../util/waveform';
|
import { interpolateArray } from '../../../util/waveform';
|
||||||
import {
|
import {
|
||||||
buildApiCurrencyAmount,
|
buildApiCurrencyAmount,
|
||||||
@ -254,6 +254,7 @@ export function buildApiMessageWithChatId(
|
|||||||
viewsCount: mtpMessage.views,
|
viewsCount: mtpMessage.views,
|
||||||
forwardsCount: mtpMessage.forwards,
|
forwardsCount: mtpMessage.forwards,
|
||||||
isScheduled,
|
isScheduled,
|
||||||
|
scheduleRepeatPeriod: mtpMessage.scheduleRepeatPeriod,
|
||||||
isFromScheduled: mtpMessage.fromScheduled,
|
isFromScheduled: mtpMessage.fromScheduled,
|
||||||
isSilent: mtpMessage.silent,
|
isSilent: mtpMessage.silent,
|
||||||
isPinned: mtpMessage.pinned,
|
isPinned: mtpMessage.pinned,
|
||||||
@ -442,6 +443,7 @@ export function buildLocalMessage(
|
|||||||
contact?: ApiContact,
|
contact?: ApiContact,
|
||||||
groupedId?: string,
|
groupedId?: string,
|
||||||
scheduledAt?: number,
|
scheduledAt?: number,
|
||||||
|
scheduleRepeatPeriod?: number,
|
||||||
sendAs?: ApiPeer,
|
sendAs?: ApiPeer,
|
||||||
story?: ApiStory | ApiStorySkipped,
|
story?: ApiStory | ApiStorySkipped,
|
||||||
isInvertedMedia?: true,
|
isInvertedMedia?: true,
|
||||||
@ -475,7 +477,7 @@ export function buildLocalMessage(
|
|||||||
pollId: localPoll?.id,
|
pollId: localPoll?.id,
|
||||||
todo: localTodo,
|
todo: localTodo,
|
||||||
}),
|
}),
|
||||||
date: scheduledAt || Math.round(Date.now() / 1000) + getServerTimeOffset(),
|
date: scheduledAt || getServerTime(),
|
||||||
isOutgoing: !isChannel,
|
isOutgoing: !isChannel,
|
||||||
senderId: chat.type !== 'chatTypePrivate' ? (sendAs?.id || currentUserId) : undefined,
|
senderId: chat.type !== 'chatTypePrivate' ? (sendAs?.id || currentUserId) : undefined,
|
||||||
replyInfo: resultReplyInfo,
|
replyInfo: resultReplyInfo,
|
||||||
@ -485,6 +487,7 @@ export function buildLocalMessage(
|
|||||||
...(media && (media.photo || media.video) && { isInAlbum: true }),
|
...(media && (media.photo || media.video) && { isInAlbum: true }),
|
||||||
}),
|
}),
|
||||||
...(scheduledAt && { isScheduled: true }),
|
...(scheduledAt && { isScheduled: true }),
|
||||||
|
scheduleRepeatPeriod,
|
||||||
isForwardingAllowed: true,
|
isForwardingAllowed: true,
|
||||||
isInvertedMedia,
|
isInvertedMedia,
|
||||||
effectId,
|
effectId,
|
||||||
@ -506,6 +509,7 @@ export function buildLocalForwardedMessage({
|
|||||||
toThreadId,
|
toThreadId,
|
||||||
message,
|
message,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
noAuthors,
|
noAuthors,
|
||||||
noCaptions,
|
noCaptions,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
@ -516,6 +520,7 @@ export function buildLocalForwardedMessage({
|
|||||||
toThreadId?: number;
|
toThreadId?: number;
|
||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
scheduledAt?: number;
|
scheduledAt?: number;
|
||||||
|
scheduleRepeatPeriod?: number;
|
||||||
noAuthors?: boolean;
|
noAuthors?: boolean;
|
||||||
noCaptions?: boolean;
|
noCaptions?: boolean;
|
||||||
isCurrentUserPremium?: boolean;
|
isCurrentUserPremium?: boolean;
|
||||||
@ -565,7 +570,8 @@ export function buildLocalForwardedMessage({
|
|||||||
id: localId,
|
id: localId,
|
||||||
chatId: toChat.id,
|
chatId: toChat.id,
|
||||||
content: updatedContent,
|
content: updatedContent,
|
||||||
date: scheduledAt || Math.round(Date.now() / 1000) + getServerTimeOffset(),
|
date: scheduledAt || getServerTime(),
|
||||||
|
scheduleRepeatPeriod,
|
||||||
isOutgoing: !asIncomingInChatWithSelf && toChat.type !== 'chatTypeChannel',
|
isOutgoing: !asIncomingInChatWithSelf && toChat.type !== 'chatTypeChannel',
|
||||||
senderId: toChat.type !== 'chatTypePrivate' ? (sendAs?.id || currentUserId) : undefined,
|
senderId: toChat.type !== 'chatTypePrivate' ? (sendAs?.id || currentUserId) : undefined,
|
||||||
sendingState: 'messageSendingStatePending',
|
sendingState: 'messageSendingStatePending',
|
||||||
|
|||||||
@ -300,7 +300,8 @@ export function sendMessageLocal(
|
|||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
chat, lastMessageId, text, entities, replyInfo, suggestedPostInfo, attachment, sticker, story, gif, poll, todo,
|
chat, lastMessageId, text, entities, replyInfo, suggestedPostInfo, attachment, sticker, story, gif, poll, todo,
|
||||||
contact, scheduledAt, groupedId, sendAs, wasDrafted, isInvertedMedia, effectId, isPending, messagePriceInStars,
|
contact, scheduledAt, scheduleRepeatPeriod, groupedId, sendAs, wasDrafted, isInvertedMedia, effectId, isPending,
|
||||||
|
messagePriceInStars,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
if (!chat) return undefined;
|
if (!chat) return undefined;
|
||||||
@ -323,6 +324,7 @@ export function sendMessageLocal(
|
|||||||
contact,
|
contact,
|
||||||
groupedId,
|
groupedId,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
sendAs,
|
sendAs,
|
||||||
story,
|
story,
|
||||||
isInvertedMedia,
|
isInvertedMedia,
|
||||||
@ -352,7 +354,7 @@ export function sendApiMessage(
|
|||||||
chat, text, entities, replyInfo, suggestedPostInfo, suggestedMedia,
|
chat, text, entities, replyInfo, suggestedPostInfo, suggestedMedia,
|
||||||
attachment, sticker, story, gif, poll, todo, contact,
|
attachment, sticker, story, gif, poll, todo, contact,
|
||||||
|
|
||||||
isSilent, scheduledAt, groupedId, noWebPage, sendAs, shouldUpdateStickerSetOrder,
|
isSilent, scheduledAt, scheduleRepeatPeriod, groupedId, noWebPage, sendAs, shouldUpdateStickerSetOrder,
|
||||||
isInvertedMedia, effectId, webPageMediaSize, webPageUrl, messagePriceInStars,
|
isInvertedMedia, effectId, webPageMediaSize, webPageUrl, messagePriceInStars,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
@ -384,6 +386,7 @@ export function sendApiMessage(
|
|||||||
groupedId,
|
groupedId,
|
||||||
isSilent,
|
isSilent,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
messagePriceInStars,
|
messagePriceInStars,
|
||||||
}, randomId, localMessage, onProgress);
|
}, randomId, localMessage, onProgress);
|
||||||
}
|
}
|
||||||
@ -484,6 +487,7 @@ export function sendApiMessage(
|
|||||||
replyTo: replyInfo && buildInputReplyTo(replyInfo),
|
replyTo: replyInfo && buildInputReplyTo(replyInfo),
|
||||||
silent: isSilent || undefined,
|
silent: isSilent || undefined,
|
||||||
scheduleDate: scheduledAt,
|
scheduleDate: scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
sendAs: sendAs && buildInputPeer(sendAs.id, sendAs.accessHash),
|
sendAs: sendAs && buildInputPeer(sendAs.id, sendAs.accessHash),
|
||||||
updateStickersetsOrder: shouldUpdateStickerSetOrder || undefined,
|
updateStickersetsOrder: shouldUpdateStickerSetOrder || undefined,
|
||||||
invertMedia: isInvertedMedia || undefined,
|
invertMedia: isInvertedMedia || undefined,
|
||||||
@ -556,6 +560,7 @@ function sendGroupedMedia(
|
|||||||
groupedId,
|
groupedId,
|
||||||
isSilent,
|
isSilent,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
sendAs,
|
sendAs,
|
||||||
messagePriceInStars,
|
messagePriceInStars,
|
||||||
}: {
|
}: {
|
||||||
@ -568,6 +573,7 @@ function sendGroupedMedia(
|
|||||||
groupedId: string;
|
groupedId: string;
|
||||||
isSilent?: boolean;
|
isSilent?: boolean;
|
||||||
scheduledAt?: number;
|
scheduledAt?: number;
|
||||||
|
scheduleRepeatPeriod?: number;
|
||||||
sendAs?: ApiPeer;
|
sendAs?: ApiPeer;
|
||||||
messagePriceInStars?: number;
|
messagePriceInStars?: number;
|
||||||
},
|
},
|
||||||
@ -645,6 +651,7 @@ function sendGroupedMedia(
|
|||||||
replyTo: replyInfo && buildInputReplyTo(replyInfo),
|
replyTo: replyInfo && buildInputReplyTo(replyInfo),
|
||||||
...(isSilent && { silent: isSilent }),
|
...(isSilent && { silent: isSilent }),
|
||||||
...(scheduledAt && { scheduleDate: scheduledAt }),
|
...(scheduledAt && { scheduleDate: scheduledAt }),
|
||||||
|
...(scheduleRepeatPeriod && { scheduleRepeatPeriod }),
|
||||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||||
...(messagePriceInStars && { allowPaidStars: BigInt(messagePriceInStars * count) }),
|
...(messagePriceInStars && { allowPaidStars: BigInt(messagePriceInStars * count) }),
|
||||||
...(suggestedPostInfo && { suggestedPost: buildInputSuggestedPost(suggestedPostInfo) }),
|
...(suggestedPostInfo && { suggestedPost: buildInputSuggestedPost(suggestedPostInfo) }),
|
||||||
@ -756,6 +763,7 @@ export async function editMessage({
|
|||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
id: message.id,
|
id: message.id,
|
||||||
...(isScheduled && { scheduleDate: message.date }),
|
...(isScheduled && { scheduleDate: message.date }),
|
||||||
|
...(message.scheduleRepeatPeriod && { scheduleRepeatPeriod: message.scheduleRepeatPeriod }),
|
||||||
...(noWebPage && { noWebpage: noWebPage }),
|
...(noWebPage && { noWebpage: noWebPage }),
|
||||||
...(isInvertedMedia && { invertMedia: isInvertedMedia }),
|
...(isInvertedMedia && { invertMedia: isInvertedMedia }),
|
||||||
}), { shouldThrow: true });
|
}), { shouldThrow: true });
|
||||||
@ -893,15 +901,18 @@ export async function rescheduleMessage({
|
|||||||
chat,
|
chat,
|
||||||
message,
|
message,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
scheduledAt: number;
|
scheduledAt: number;
|
||||||
|
scheduleRepeatPeriod?: number;
|
||||||
}) {
|
}) {
|
||||||
await invokeRequest(new GramJs.messages.EditMessage({
|
await invokeRequest(new GramJs.messages.EditMessage({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
id: message.id,
|
id: message.id,
|
||||||
scheduleDate: scheduledAt,
|
scheduleDate: scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1842,7 +1853,7 @@ export async function fetchExtendedMedia({
|
|||||||
export function forwardMessagesLocal(params: ForwardMessagesParams) {
|
export function forwardMessagesLocal(params: ForwardMessagesParams) {
|
||||||
const {
|
const {
|
||||||
toChat, toThreadId, messages,
|
toChat, toThreadId, messages,
|
||||||
scheduledAt, sendAs, noAuthors, noCaptions,
|
scheduledAt, scheduleRepeatPeriod, sendAs, noAuthors, noCaptions,
|
||||||
isCurrentUserPremium, wasDrafted, lastMessageId,
|
isCurrentUserPremium, wasDrafted, lastMessageId,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
@ -1855,6 +1866,7 @@ export function forwardMessagesLocal(params: ForwardMessagesParams) {
|
|||||||
toThreadId: Number(toThreadId),
|
toThreadId: Number(toThreadId),
|
||||||
message,
|
message,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
noAuthors,
|
noAuthors,
|
||||||
noCaptions,
|
noCaptions,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
@ -1877,7 +1889,7 @@ export function forwardMessagesLocal(params: ForwardMessagesParams) {
|
|||||||
export async function forwardApiMessages(params: ForwardMessagesParams) {
|
export async function forwardApiMessages(params: ForwardMessagesParams) {
|
||||||
const {
|
const {
|
||||||
fromChat, toChat, toThreadId, isSilent,
|
fromChat, toChat, toThreadId, isSilent,
|
||||||
scheduledAt, sendAs, withMyScore, noAuthors, noCaptions,
|
scheduledAt, scheduleRepeatPeriod, sendAs, withMyScore, noAuthors, noCaptions,
|
||||||
forwardedLocalMessagesSlice, messagePriceInStars,
|
forwardedLocalMessagesSlice, messagePriceInStars,
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
@ -1902,6 +1914,7 @@ export async function forwardApiMessages(params: ForwardMessagesParams) {
|
|||||||
dropMediaCaptions: noCaptions || undefined,
|
dropMediaCaptions: noCaptions || undefined,
|
||||||
...(toThreadId && { topMsgId: Number(toThreadId) }),
|
...(toThreadId && { topMsgId: Number(toThreadId) }),
|
||||||
...(scheduledAt && { scheduleDate: scheduledAt }),
|
...(scheduledAt && { scheduleDate: scheduledAt }),
|
||||||
|
...(scheduleRepeatPeriod && { scheduleRepeatPeriod }),
|
||||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||||
...(priceInStars && { allowPaidStars: BigInt(priceInStars) }),
|
...(priceInStars && { allowPaidStars: BigInt(priceInStars) }),
|
||||||
}), {
|
}), {
|
||||||
|
|||||||
@ -648,6 +648,7 @@ export interface ApiMessage {
|
|||||||
viaBusinessBotId?: string;
|
viaBusinessBotId?: string;
|
||||||
postAuthorTitle?: string;
|
postAuthorTitle?: string;
|
||||||
isScheduled?: boolean;
|
isScheduled?: boolean;
|
||||||
|
scheduleRepeatPeriod?: number;
|
||||||
shouldHideKeyboardButtons?: boolean;
|
shouldHideKeyboardButtons?: boolean;
|
||||||
isHideKeyboardSelective?: boolean;
|
isHideKeyboardSelective?: boolean;
|
||||||
isFromScheduled?: boolean;
|
isFromScheduled?: boolean;
|
||||||
|
|||||||
@ -1167,6 +1167,16 @@
|
|||||||
"SecretChat" = "Secret Chat";
|
"SecretChat" = "Secret Chat";
|
||||||
"ChannelPermissionDeniedSendMessagesUntil" = "The admins of this group have restricted you from messaging until {date}.";
|
"ChannelPermissionDeniedSendMessagesUntil" = "The admins of this group have restricted you from messaging until {date}.";
|
||||||
"FormatDateAtTime" = "{date} at {time}";
|
"FormatDateAtTime" = "{date} at {time}";
|
||||||
|
"MessageRepeatPeriodEveryMinutes_one" = "every minute";
|
||||||
|
"MessageRepeatPeriodEveryMinutes_other" = "every {count} minutes";
|
||||||
|
"MessageRepeatPeriodDaily" = "daily";
|
||||||
|
"MessageRepeatPeriodWeekly" = "weekly";
|
||||||
|
"MessageRepeatPeriodBiweekly" = "biweekly";
|
||||||
|
"MessageRepeatPeriodMonthly" = "monthly";
|
||||||
|
"MessageRepeatPeriodEveryMonths_one" = "every month";
|
||||||
|
"MessageRepeatPeriodEveryMonths_other" = "every {count} months";
|
||||||
|
"MessageRepeatPeriodYearly" = "yearly";
|
||||||
|
"MessageScheduledRepeatPremium" = "Subscribe to **Telegram Premium** to schedule repeating messages.";
|
||||||
"StickerPackRemoveStickerCount_one" = "Remove {count} sticker";
|
"StickerPackRemoveStickerCount_one" = "Remove {count} sticker";
|
||||||
"StickerPackRemoveStickerCount_other" = "Remove {count} stickers";
|
"StickerPackRemoveStickerCount_other" = "Remove {count} stickers";
|
||||||
"StickerPackAddStickerCount_one" = "Add {count} sticker";
|
"StickerPackAddStickerCount_one" = "Add {count} sticker";
|
||||||
@ -1225,6 +1235,17 @@
|
|||||||
"Archive" = "Archive";
|
"Archive" = "Archive";
|
||||||
"WaitingForNetwork" = "Waiting for network...";
|
"WaitingForNetwork" = "Waiting for network...";
|
||||||
"ScheduleSendWhenOnline" = "Send When Online";
|
"ScheduleSendWhenOnline" = "Send When Online";
|
||||||
|
"ScheduleRepeat" = "Repeat: {value}";
|
||||||
|
"ScheduleRepeatNever" = "Never";
|
||||||
|
"ScheduleRepeatEveryMinutes_one" = "Every Minute";
|
||||||
|
"ScheduleRepeatEveryMinutes_other" = "Every {count} Minutes";
|
||||||
|
"ScheduleRepeatDaily" = "Daily";
|
||||||
|
"ScheduleRepeatWeekly" = "Weekly";
|
||||||
|
"ScheduleRepeatBiweekly" = "Biweekly";
|
||||||
|
"ScheduleRepeatMonthly" = "Monthly";
|
||||||
|
"ScheduleRepeatEveryMonths_one" = "Every {count} Month";
|
||||||
|
"ScheduleRepeatEveryMonths_other" = "Every {count} Months";
|
||||||
|
"ScheduleRepeatYearly" = "Yearly";
|
||||||
"VoipIncoming" = "Incoming call";
|
"VoipIncoming" = "Incoming call";
|
||||||
"Years_one" = "{count} year";
|
"Years_one" = "{count} year";
|
||||||
"Years_other" = "{count} years";
|
"Years_other" = "{count} years";
|
||||||
|
|||||||
@ -6,6 +6,28 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.repeat-mode {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.repeat-mode-button {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.drop-down-icon {
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expanded-icon {
|
||||||
|
transform: rotate(-180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.repeated-mode-transition-slide {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.timepicker {
|
.timepicker {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|||||||
@ -1,20 +1,29 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import type React from '../../lib/teact/teact';
|
import type React from '../../lib/teact/teact';
|
||||||
import {
|
import {
|
||||||
memo, useCallback, useEffect, useMemo, useState,
|
memo, useCallback, useEffect, useMemo, useRef, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { OldLangFn } from '../../hooks/useOldLang';
|
import type { OldLangFn } from '../../hooks/useOldLang';
|
||||||
|
import type { RepeatedMessageMode } from '../../util/scheduledMessages';
|
||||||
|
|
||||||
import { MAX_INT_32 } from '../../config';
|
import { MAX_INT_32 } from '../../config';
|
||||||
|
import { selectIsCurrentUserPremium } from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { formatDateToString, formatTime, getDayStart } from '../../util/dates/dateFormat';
|
import { formatDateToString, formatTime, getDayStart } from '../../util/dates/dateFormat';
|
||||||
|
import { ALL_REPEAT_MODES, getScheduleRepeatModeText, TEST_SERVER_ONLY_MODES } from '../../util/scheduledMessages';
|
||||||
|
|
||||||
|
import useContextMenuHandlers from '../../hooks/useContextMenuHandlers';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
|
import useLang from '../../hooks/useLang';
|
||||||
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
||||||
|
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
|
import Menu from '../ui/Menu';
|
||||||
|
import MenuItem from '../ui/MenuItem';
|
||||||
import Modal from '../ui/Modal';
|
import Modal from '../ui/Modal';
|
||||||
|
|
||||||
import './CalendarModal.scss';
|
import './CalendarModal.scss';
|
||||||
@ -30,15 +39,22 @@ export type OwnProps = {
|
|||||||
isPastMode?: boolean;
|
isPastMode?: boolean;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
withTimePicker?: boolean;
|
withTimePicker?: boolean;
|
||||||
|
withRepeatMode?: boolean;
|
||||||
|
initialRepeatMode?: RepeatedMessageMode;
|
||||||
submitButtonLabel?: string;
|
submitButtonLabel?: string;
|
||||||
secondButtonLabel?: string;
|
secondButtonLabel?: string;
|
||||||
description?: string;
|
description?: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onSubmit: (date: Date) => void;
|
onSubmit: (date: Date, repeatMode?: RepeatedMessageMode) => void;
|
||||||
onDateChange?: (date: Date) => void;
|
onDateChange?: (date: Date) => void;
|
||||||
onSecondButtonClick?: NoneToVoidFunction;
|
onSecondButtonClick?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type StateProps = {
|
||||||
|
isTestServer?: boolean;
|
||||||
|
isCurrentUserPremium?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const WEEKDAY_LETTERS = [
|
const WEEKDAY_LETTERS = [
|
||||||
'lng_weekday1',
|
'lng_weekday1',
|
||||||
'lng_weekday2',
|
'lng_weekday2',
|
||||||
@ -49,7 +65,7 @@ const WEEKDAY_LETTERS = [
|
|||||||
'lng_weekday7',
|
'lng_weekday7',
|
||||||
];
|
];
|
||||||
|
|
||||||
const CalendarModal: FC<OwnProps> = ({
|
const CalendarModal: FC<OwnProps & StateProps> = ({
|
||||||
selectedAt,
|
selectedAt,
|
||||||
minAt,
|
minAt,
|
||||||
maxAt,
|
maxAt,
|
||||||
@ -57,17 +73,39 @@ const CalendarModal: FC<OwnProps> = ({
|
|||||||
isPastMode,
|
isPastMode,
|
||||||
isOpen,
|
isOpen,
|
||||||
withTimePicker,
|
withTimePicker,
|
||||||
|
withRepeatMode,
|
||||||
|
initialRepeatMode,
|
||||||
submitButtonLabel,
|
submitButtonLabel,
|
||||||
secondButtonLabel,
|
secondButtonLabel,
|
||||||
description,
|
description,
|
||||||
|
isTestServer,
|
||||||
|
isCurrentUserPremium,
|
||||||
onClose,
|
onClose,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
onDateChange,
|
onDateChange,
|
||||||
onSecondButtonClick,
|
onSecondButtonClick,
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useOldLang();
|
const { showNotification } = getActions();
|
||||||
|
|
||||||
|
const menuRef = useRef<HTMLDivElement>();
|
||||||
|
const dialogRef = useRef<HTMLDivElement>();
|
||||||
|
|
||||||
|
const oldLang = useOldLang();
|
||||||
|
const lang = useLang();
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
|
|
||||||
|
const {
|
||||||
|
isContextMenuOpen: isRepeatMenuOpen,
|
||||||
|
contextMenuAnchor: repeatMenuAnchor,
|
||||||
|
handleContextMenu,
|
||||||
|
handleContextMenuClose,
|
||||||
|
handleContextMenuHide,
|
||||||
|
} = useContextMenuHandlers(menuRef);
|
||||||
|
|
||||||
|
const getRootElement = useLastCallback(() => dialogRef.current);
|
||||||
|
const getMenuElement = useLastCallback(() => menuRef.current!.querySelector('.bubble'));
|
||||||
|
const getTriggerElement = useLastCallback(() => dialogRef.current!.querySelector('.repeat-mode-button'));
|
||||||
|
|
||||||
const minDate = useMemo(() => {
|
const minDate = useMemo(() => {
|
||||||
if (isFutureMode && !minAt) return new Date();
|
if (isFutureMode && !minAt) return new Date();
|
||||||
return new Date(Math.max(minAt || MIN_SAFE_DATE, MIN_SAFE_DATE));
|
return new Date(Math.max(minAt || MIN_SAFE_DATE, MIN_SAFE_DATE));
|
||||||
@ -91,6 +129,9 @@ const CalendarModal: FC<OwnProps> = ({
|
|||||||
const [selectedMinutes, setSelectedMinutes] = useState<string>(() => (
|
const [selectedMinutes, setSelectedMinutes] = useState<string>(() => (
|
||||||
formatInputTime(passedSelectedDate.getMinutes())
|
formatInputTime(passedSelectedDate.getMinutes())
|
||||||
));
|
));
|
||||||
|
const [repeatedMessageMode, setRepeatedMessageMode] = useState<RepeatedMessageMode>(
|
||||||
|
initialRepeatMode || 'never',
|
||||||
|
);
|
||||||
|
|
||||||
const selectedDay = formatDay(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate());
|
const selectedDay = formatDay(selectedDate.getFullYear(), selectedDate.getMonth(), selectedDate.getDate());
|
||||||
const currentYear = currentMonthAndYear.getFullYear();
|
const currentYear = currentMonthAndYear.getFullYear();
|
||||||
@ -99,6 +140,16 @@ const CalendarModal: FC<OwnProps> = ({
|
|||||||
const isDisabled = (isFutureMode && selectedDate.getTime() < minDate.getTime())
|
const isDisabled = (isFutureMode && selectedDate.getTime() < minDate.getTime())
|
||||||
|| (isPastMode && selectedDate.getTime() > maxDate.getTime());
|
|| (isPastMode && selectedDate.getTime() > maxDate.getTime());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setRepeatedMessageMode('never');
|
||||||
|
}
|
||||||
|
}, [isOpen]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setRepeatedMessageMode(initialRepeatMode || 'never');
|
||||||
|
}, [initialRepeatMode]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!prevIsOpen && isOpen) {
|
if (!prevIsOpen && isOpen) {
|
||||||
setSelectedDate(passedSelectedDate);
|
setSelectedDate(passedSelectedDate);
|
||||||
@ -144,8 +195,23 @@ const CalendarModal: FC<OwnProps> = ({
|
|||||||
), [currentMonth, currentYear]);
|
), [currentMonth, currentYear]);
|
||||||
|
|
||||||
const submitLabel = useMemo(() => {
|
const submitLabel = useMemo(() => {
|
||||||
return submitButtonLabel || formatSubmitLabel(lang, selectedDate);
|
return submitButtonLabel || formatSubmitLabel(oldLang, selectedDate);
|
||||||
}, [lang, selectedDate, submitButtonLabel]);
|
}, [oldLang, selectedDate, submitButtonLabel]);
|
||||||
|
|
||||||
|
const handleRepeatModeClick = useLastCallback((e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
if (!isCurrentUserPremium) {
|
||||||
|
showNotification({
|
||||||
|
message: lang('MessageScheduledRepeatPremium'),
|
||||||
|
action: {
|
||||||
|
action: 'openPremiumModal',
|
||||||
|
payload: { },
|
||||||
|
},
|
||||||
|
actionText: lang('PremiumMore'),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
handleContextMenu(e);
|
||||||
|
});
|
||||||
|
|
||||||
function handlePrevMonth() {
|
function handlePrevMonth() {
|
||||||
setCurrentMonthAndYear((d) => {
|
setCurrentMonthAndYear((d) => {
|
||||||
@ -178,14 +244,15 @@ const CalendarModal: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const handleSubmit = useCallback(() => {
|
const handleSubmit = useCallback(() => {
|
||||||
|
const repeatMode = withRepeatMode && repeatedMessageMode !== 'never' ? repeatedMessageMode : undefined;
|
||||||
if (isFutureMode && selectedDate < minDate) {
|
if (isFutureMode && selectedDate < minDate) {
|
||||||
onSubmit(minDate);
|
onSubmit(minDate, repeatMode);
|
||||||
} else if (isPastMode && selectedDate > maxDate) {
|
} else if (isPastMode && selectedDate > maxDate) {
|
||||||
onSubmit(maxDate);
|
onSubmit(maxDate, repeatMode);
|
||||||
} else {
|
} else {
|
||||||
onSubmit(selectedDate);
|
onSubmit(selectedDate, repeatMode);
|
||||||
}
|
}
|
||||||
}, [isFutureMode, isPastMode, minDate, maxDate, onSubmit, selectedDate]);
|
}, [isFutureMode, isPastMode, minDate, maxDate, onSubmit, selectedDate, withRepeatMode, repeatedMessageMode]);
|
||||||
|
|
||||||
const handleChangeHours = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleChangeHours = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const value = e.target.value.replace(/[^\d]+/g, '');
|
const value = e.target.value.replace(/[^\d]+/g, '');
|
||||||
@ -227,6 +294,18 @@ const CalendarModal: FC<OwnProps> = ({
|
|||||||
e.target.value = minutesStr;
|
e.target.value = minutesStr;
|
||||||
}, [selectedDate, onDateChange]);
|
}, [selectedDate, onDateChange]);
|
||||||
|
|
||||||
|
const renderRepeatMenuItem = useCallback((mode: RepeatedMessageMode) => {
|
||||||
|
return (
|
||||||
|
<MenuItem key={mode} onClick={() => setRepeatedMessageMode(mode)}>
|
||||||
|
{getScheduleRepeatModeText(mode, lang)}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
}, [lang]);
|
||||||
|
|
||||||
|
const availableRepeatModes = useMemo(() => {
|
||||||
|
return ALL_REPEAT_MODES.filter((mode) => isTestServer || !TEST_SERVER_ONLY_MODES.has(mode));
|
||||||
|
}, [isTestServer]);
|
||||||
|
|
||||||
function renderTimePicker() {
|
function renderTimePicker() {
|
||||||
return (
|
return (
|
||||||
<div className="timepicker">
|
<div className="timepicker">
|
||||||
@ -251,12 +330,46 @@ const CalendarModal: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function renderRepeatMode() {
|
||||||
|
const dropDownIconClass = buildClassName('drop-down-icon', !isRepeatMenuOpen && 'expanded-icon');
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="repeat-mode" ref={menuRef}>
|
||||||
|
<Button
|
||||||
|
className="repeat-mode-button"
|
||||||
|
onClick={handleRepeatModeClick}
|
||||||
|
noForcedUpperCase
|
||||||
|
isText
|
||||||
|
iconName={isCurrentUserPremium ? 'down' : 'lock-badge'}
|
||||||
|
iconClassName={isCurrentUserPremium ? dropDownIconClass : undefined}
|
||||||
|
iconAlignment="end"
|
||||||
|
>
|
||||||
|
{lang('ScheduleRepeat', { value: getScheduleRepeatModeText(repeatedMessageMode, lang) })}
|
||||||
|
</Button>
|
||||||
|
<Menu
|
||||||
|
isOpen={isRepeatMenuOpen}
|
||||||
|
className="with-menu-transitions"
|
||||||
|
anchor={repeatMenuAnchor}
|
||||||
|
getTriggerElement={getTriggerElement}
|
||||||
|
getRootElement={getRootElement}
|
||||||
|
getMenuElement={getMenuElement}
|
||||||
|
onClose={handleContextMenuClose}
|
||||||
|
onCloseAnimationEnd={handleContextMenuHide}
|
||||||
|
autoClose
|
||||||
|
>
|
||||||
|
{availableRepeatModes.map(renderRepeatMenuItem)}
|
||||||
|
</Menu>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
className="CalendarModal"
|
className="CalendarModal"
|
||||||
onEnter={handleSubmit}
|
onEnter={handleSubmit}
|
||||||
|
dialogRef={dialogRef}
|
||||||
>
|
>
|
||||||
<div className="container">
|
<div className="container">
|
||||||
<div className="month-selector">
|
<div className="month-selector">
|
||||||
@ -269,7 +382,7 @@ const CalendarModal: FC<OwnProps> = ({
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
<h4>
|
<h4>
|
||||||
{lang(`lng_month${currentMonth + 1}`)}
|
{oldLang(`lng_month${currentMonth + 1}`)}
|
||||||
{' '}
|
{' '}
|
||||||
{currentYear}
|
{currentYear}
|
||||||
</h4>
|
</h4>
|
||||||
@ -298,7 +411,7 @@ const CalendarModal: FC<OwnProps> = ({
|
|||||||
<div className="calendar-grid">
|
<div className="calendar-grid">
|
||||||
{WEEKDAY_LETTERS.map((day) => (
|
{WEEKDAY_LETTERS.map((day) => (
|
||||||
<div className="day-button faded weekday">
|
<div className="day-button faded weekday">
|
||||||
<span>{lang(day)}</span>
|
<span>{oldLang(day)}</span>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{prevMonthGrid.map((gridDate) => (
|
{prevMonthGrid.map((gridDate) => (
|
||||||
@ -332,6 +445,7 @@ const CalendarModal: FC<OwnProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{withTimePicker && renderTimePicker()}
|
{withTimePicker && renderTimePicker()}
|
||||||
|
{withRepeatMode && isOpen && renderRepeatMode()}
|
||||||
|
|
||||||
<div className="footer">
|
<div className="footer">
|
||||||
{description && (
|
{description && (
|
||||||
@ -422,4 +536,13 @@ function formatSubmitLabel(lang: OldLangFn, date: Date) {
|
|||||||
return lang('Conversation.ScheduleMessage.SendOn', [day, formatTime(lang, date)]);
|
return lang('Conversation.ScheduleMessage.SendOn', [day, formatTime(lang, date)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(CalendarModal);
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
(global): Complete<StateProps> => {
|
||||||
|
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
||||||
|
|
||||||
|
return {
|
||||||
|
isTestServer: global.config?.isTestServer,
|
||||||
|
isCurrentUserPremium,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
)(CalendarModal));
|
||||||
|
|||||||
@ -1079,6 +1079,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
sendGrouped = attachmentSettings.shouldSendGrouped,
|
sendGrouped = attachmentSettings.shouldSendGrouped,
|
||||||
isSilent,
|
isSilent,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
isInvertedMedia,
|
isInvertedMedia,
|
||||||
}: {
|
}: {
|
||||||
attachments: ApiAttachment[];
|
attachments: ApiAttachment[];
|
||||||
@ -1086,6 +1087,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
sendGrouped?: boolean;
|
sendGrouped?: boolean;
|
||||||
isSilent?: boolean;
|
isSilent?: boolean;
|
||||||
scheduledAt?: number;
|
scheduledAt?: number;
|
||||||
|
scheduleRepeatPeriod?: number;
|
||||||
isInvertedMedia?: true;
|
isInvertedMedia?: true;
|
||||||
}) => {
|
}) => {
|
||||||
if (!currentMessageList && !storyId) {
|
if (!currentMessageList && !storyId) {
|
||||||
@ -1110,6 +1112,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
text,
|
text,
|
||||||
entities,
|
entities,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
isSilent,
|
isSilent,
|
||||||
shouldUpdateStickerSetOrder,
|
shouldUpdateStickerSetOrder,
|
||||||
attachments: prepareAttachmentsToSend(attachmentsToSend, sendCompressed),
|
attachments: prepareAttachmentsToSend(attachmentsToSend, sendCompressed),
|
||||||
@ -1159,6 +1162,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
isSilent?: boolean,
|
isSilent?: boolean,
|
||||||
scheduledAt?: number,
|
scheduledAt?: number,
|
||||||
isInvertedMedia?: true,
|
isInvertedMedia?: true,
|
||||||
|
scheduleRepeatPeriod?: number,
|
||||||
) => {
|
) => {
|
||||||
if (canSendAttachments(attachments)) {
|
if (canSendAttachments(attachments)) {
|
||||||
sendAttachments({
|
sendAttachments({
|
||||||
@ -1167,13 +1171,19 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
sendGrouped,
|
sendGrouped,
|
||||||
isSilent,
|
isSilent,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
isInvertedMedia,
|
isInvertedMedia,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSendCore = useLastCallback(
|
const handleSendCore = useLastCallback(
|
||||||
(currentAttachments: ApiAttachment[], isSilent = false, scheduledAt?: number) => {
|
(
|
||||||
|
currentAttachments: ApiAttachment[],
|
||||||
|
isSilent = false,
|
||||||
|
scheduledAt?: number,
|
||||||
|
scheduleRepeatPeriod?: number,
|
||||||
|
) => {
|
||||||
const { text, entities } = parseHtmlAsFormattedText(getHtml());
|
const { text, entities } = parseHtmlAsFormattedText(getHtml());
|
||||||
|
|
||||||
if (currentAttachments.length) {
|
if (currentAttachments.length) {
|
||||||
@ -1181,6 +1191,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
sendAttachments({
|
sendAttachments({
|
||||||
attachments: currentAttachments,
|
attachments: currentAttachments,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
isSilent,
|
isSilent,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1209,6 +1220,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
text,
|
text,
|
||||||
entities,
|
entities,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
isSilent,
|
isSilent,
|
||||||
shouldUpdateStickerSetOrder,
|
shouldUpdateStickerSetOrder,
|
||||||
isInvertedMedia,
|
isInvertedMedia,
|
||||||
@ -1235,7 +1247,11 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSend = useLastCallback(async (isSilent = false, scheduledAt?: number) => {
|
const handleSend = useLastCallback(async (
|
||||||
|
isSilent = false,
|
||||||
|
scheduledAt?: number,
|
||||||
|
scheduleRepeatPeriod?: number,
|
||||||
|
) => {
|
||||||
if (!currentMessageList && !storyId) {
|
if (!currentMessageList && !storyId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1257,11 +1273,15 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSendCore(currentAttachments, isSilent, scheduledAt);
|
handleSendCore(currentAttachments, isSilent, scheduledAt, scheduleRepeatPeriod);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSendWithConfirmation = useLastCallback((isSilent = false, scheduledAt?: number) => {
|
const handleSendWithConfirmation = useLastCallback((
|
||||||
handleActionWithPaymentConfirmation(handleSend, isSilent, scheduledAt);
|
isSilent = false,
|
||||||
|
scheduledAt?: number,
|
||||||
|
scheduleRepeatPeriod?: number,
|
||||||
|
) => {
|
||||||
|
handleActionWithPaymentConfirmation(handleSend, isSilent, scheduledAt, scheduleRepeatPeriod);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleTodoListCreate = useLastCallback(() => {
|
const handleTodoListCreate = useLastCallback(() => {
|
||||||
@ -1302,7 +1322,11 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleMessageSchedule = useLastCallback((
|
const handleMessageSchedule = useLastCallback((
|
||||||
args: ScheduledMessageArgs, scheduledAt: number, messageList: MessageList, effectId?: string,
|
args: ScheduledMessageArgs,
|
||||||
|
scheduledAt: number,
|
||||||
|
scheduleRepeatPeriod: number | undefined,
|
||||||
|
messageList: MessageList,
|
||||||
|
effectId?: string,
|
||||||
) => {
|
) => {
|
||||||
if (args && 'queryId' in args) {
|
if (args && 'queryId' in args) {
|
||||||
const { id, queryId, isSilent } = args;
|
const { id, queryId, isSilent } = args;
|
||||||
@ -1320,15 +1344,17 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
const { isSilent, ...restArgs } = args || {};
|
const { isSilent, ...restArgs } = args || {};
|
||||||
|
|
||||||
if (!args || Object.keys(restArgs).length === 0) {
|
if (!args || Object.keys(restArgs).length === 0) {
|
||||||
void handleSend(Boolean(isSilent), scheduledAt);
|
void handleSend(Boolean(isSilent), scheduledAt, scheduleRepeatPeriod);
|
||||||
} else if (args.sendCompressed !== undefined || args.sendGrouped !== undefined) {
|
} else if (args.sendCompressed !== undefined || args.sendGrouped !== undefined) {
|
||||||
const { sendCompressed = false, sendGrouped = false, isInvertedMedia } = args;
|
const { sendCompressed = false, sendGrouped = false, isInvertedMedia } = args;
|
||||||
void handleSendAttachments(sendCompressed, sendGrouped, isSilent, scheduledAt, isInvertedMedia);
|
void handleSendAttachments(sendCompressed, sendGrouped, isSilent, scheduledAt, isInvertedMedia,
|
||||||
|
scheduleRepeatPeriod);
|
||||||
} else {
|
} else {
|
||||||
sendMessage({
|
sendMessage({
|
||||||
...args,
|
...args,
|
||||||
messageList,
|
messageList,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
effectId,
|
effectId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -1336,8 +1362,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
useEffectWithPrevDeps(([prevContentToBeScheduled]) => {
|
useEffectWithPrevDeps(([prevContentToBeScheduled]) => {
|
||||||
if (currentMessageList && contentToBeScheduled && contentToBeScheduled !== prevContentToBeScheduled) {
|
if (currentMessageList && contentToBeScheduled && contentToBeScheduled !== prevContentToBeScheduled) {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt, scheduleRepeatPeriod) => {
|
||||||
handleMessageSchedule(contentToBeScheduled, scheduledAt, currentMessageList);
|
handleMessageSchedule(contentToBeScheduled, scheduledAt, scheduleRepeatPeriod, currentMessageList, undefined);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [contentToBeScheduled, currentMessageList, handleMessageSchedule, requestCalendar]);
|
}, [contentToBeScheduled, currentMessageList, handleMessageSchedule, requestCalendar]);
|
||||||
@ -1393,9 +1419,15 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
if (isInScheduledList || isScheduleRequested) {
|
if (isInScheduledList || isScheduleRequested) {
|
||||||
forceShowSymbolMenu();
|
forceShowSymbolMenu();
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt, scheduleRepeatPeriod) => {
|
||||||
cancelForceShowSymbolMenu();
|
cancelForceShowSymbolMenu();
|
||||||
handleActionWithPaymentConfirmation(handleMessageSchedule, { gif, isSilent }, scheduledAt, currentMessageList!);
|
handleActionWithPaymentConfirmation(
|
||||||
|
handleMessageSchedule,
|
||||||
|
{ gif, isSilent },
|
||||||
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
|
currentMessageList!,
|
||||||
|
);
|
||||||
requestMeasure(() => {
|
requestMeasure(() => {
|
||||||
resetComposer(true);
|
resetComposer(true);
|
||||||
});
|
});
|
||||||
@ -1430,10 +1462,14 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
if (isInScheduledList || isScheduleRequested) {
|
if (isInScheduledList || isScheduleRequested) {
|
||||||
forceShowSymbolMenu();
|
forceShowSymbolMenu();
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt, scheduleRepeatPeriod) => {
|
||||||
cancelForceShowSymbolMenu();
|
cancelForceShowSymbolMenu();
|
||||||
handleActionWithPaymentConfirmation(
|
handleActionWithPaymentConfirmation(
|
||||||
handleMessageSchedule, { sticker, isSilent }, scheduledAt, currentMessageList!,
|
handleMessageSchedule,
|
||||||
|
{ sticker, isSilent },
|
||||||
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
|
currentMessageList!,
|
||||||
);
|
);
|
||||||
requestMeasure(() => {
|
requestMeasure(() => {
|
||||||
resetComposer(shouldPreserveInput);
|
resetComposer(shouldPreserveInput);
|
||||||
@ -1467,7 +1503,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
isSilent = isSilent || isSilentPosting;
|
isSilent = isSilent || isSilentPosting;
|
||||||
|
|
||||||
if (isInScheduledList || isScheduleRequested) {
|
if (isInScheduledList || isScheduleRequested) {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt, scheduleRepeatPeriod) => {
|
||||||
handleActionWithPaymentConfirmation(
|
handleActionWithPaymentConfirmation(
|
||||||
handleMessageSchedule,
|
handleMessageSchedule,
|
||||||
{
|
{
|
||||||
@ -1476,6 +1512,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
isSilent,
|
isSilent,
|
||||||
},
|
},
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
currentMessageList!,
|
currentMessageList!,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -1516,11 +1553,12 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isInScheduledList) {
|
if (isInScheduledList) {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt, scheduleRepeatPeriod) => {
|
||||||
handleActionWithPaymentConfirmation(
|
handleActionWithPaymentConfirmation(
|
||||||
handleMessageSchedule,
|
handleMessageSchedule,
|
||||||
{ poll },
|
{ poll },
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
currentMessageList,
|
currentMessageList,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -1540,11 +1578,12 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isInScheduledList) {
|
if (isInScheduledList) {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt, scheduleRepeatPeriod) => {
|
||||||
handleActionWithPaymentConfirmation(
|
handleActionWithPaymentConfirmation(
|
||||||
handleMessageSchedule,
|
handleMessageSchedule,
|
||||||
{ todo },
|
{ todo },
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
currentMessageList,
|
currentMessageList,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -1558,8 +1597,13 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const sendSilent = useLastCallback((additionalArgs?: ScheduledMessageArgs) => {
|
const sendSilent = useLastCallback((additionalArgs?: ScheduledMessageArgs) => {
|
||||||
if (isInScheduledList) {
|
if (isInScheduledList) {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt, scheduleRepeatPeriod) => {
|
||||||
handleMessageSchedule({ ...additionalArgs, isSilent: true }, scheduledAt, currentMessageList!);
|
handleMessageSchedule(
|
||||||
|
{ ...additionalArgs, isSilent: true },
|
||||||
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
|
currentMessageList!,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
} else if (additionalArgs && ('sendCompressed' in additionalArgs || 'sendGrouped' in additionalArgs)) {
|
} else if (additionalArgs && ('sendCompressed' in additionalArgs || 'sendGrouped' in additionalArgs)) {
|
||||||
const { sendCompressed = false, sendGrouped = false, isInvertedMedia } = additionalArgs;
|
const { sendCompressed = false, sendGrouped = false, isInvertedMedia } = additionalArgs;
|
||||||
@ -1779,8 +1823,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
if (!currentMessageList) {
|
if (!currentMessageList) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt, scheduleRepeatPeriod) => {
|
||||||
handleMessageSchedule({}, scheduledAt, currentMessageList, effect?.id);
|
handleMessageSchedule({}, scheduledAt, scheduleRepeatPeriod, currentMessageList, effect?.id);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -1870,8 +1914,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleSendScheduled = useLastCallback(() => {
|
const handleSendScheduled = useLastCallback(() => {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt, scheduleRepeatPeriod) => {
|
||||||
handleMessageSchedule({}, scheduledAt, currentMessageList!);
|
handleMessageSchedule({}, scheduledAt, scheduleRepeatPeriod, currentMessageList!, undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1881,18 +1925,20 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const handleSendWhenOnline = useLastCallback(() => {
|
const handleSendWhenOnline = useLastCallback(() => {
|
||||||
handleActionWithPaymentConfirmation(
|
handleActionWithPaymentConfirmation(
|
||||||
handleMessageSchedule, {}, SCHEDULED_WHEN_ONLINE, currentMessageList!, effect?.id,
|
handleMessageSchedule, {}, SCHEDULED_WHEN_ONLINE, undefined, currentMessageList!, effect?.id,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSendScheduledAttachments = useLastCallback(
|
const handleSendScheduledAttachments = useLastCallback(
|
||||||
(sendCompressed: boolean, sendGrouped: boolean, isInvertedMedia?: true) => {
|
(sendCompressed: boolean, sendGrouped: boolean, isInvertedMedia?: true) => {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt, scheduleRepeatPeriod) => {
|
||||||
handleActionWithPaymentConfirmation(
|
handleActionWithPaymentConfirmation(
|
||||||
handleMessageSchedule,
|
handleMessageSchedule,
|
||||||
{ sendCompressed, sendGrouped, isInvertedMedia },
|
{ sendCompressed, sendGrouped, isInvertedMedia },
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
currentMessageList!,
|
currentMessageList!,
|
||||||
|
undefined,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
@ -125,9 +125,9 @@ const StickerSetModal: FC<OwnProps & StateProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (shouldSchedule || isScheduleRequested) {
|
if (shouldSchedule || isScheduleRequested) {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt, scheduleRepeatPeriod) => {
|
||||||
sendMessage({
|
sendMessage({
|
||||||
messageList: currentMessageList, sticker, isSilent, scheduledAt,
|
messageList: currentMessageList, sticker, isSilent, scheduledAt, scheduleRepeatPeriod,
|
||||||
});
|
});
|
||||||
onClose();
|
onClose();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -287,7 +287,12 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
const [isPinModalOpen, setIsPinModalOpen] = useState(false);
|
const [isPinModalOpen, setIsPinModalOpen] = useState(false);
|
||||||
const [isClosePollDialogOpen, openClosePollDialog, closeClosePollDialog] = useFlag();
|
const [isClosePollDialogOpen, openClosePollDialog, closeClosePollDialog] = useFlag();
|
||||||
const [selectionQuoteOffset, setSelectionQuoteOffset] = useState(UNQUOTABLE_OFFSET);
|
const [selectionQuoteOffset, setSelectionQuoteOffset] = useState(UNQUOTABLE_OFFSET);
|
||||||
const [requestCalendar, calendar] = useSchedule(canScheduleUntilOnline, onClose, message.date);
|
const [requestCalendar, calendar] = useSchedule(
|
||||||
|
canScheduleUntilOnline,
|
||||||
|
onClose,
|
||||||
|
message.date,
|
||||||
|
message.scheduleRepeatPeriod,
|
||||||
|
);
|
||||||
|
|
||||||
// `undefined` indicates that emoji are present and loading
|
// `undefined` indicates that emoji are present and loading
|
||||||
const hasCustomEmoji = customEmojiSetsInfo === undefined || Boolean(customEmojiSetsInfo.length);
|
const hasCustomEmoji = customEmojiSetsInfo === undefined || Boolean(customEmojiSetsInfo.length);
|
||||||
@ -535,11 +540,15 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
closeMenu();
|
closeMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleRescheduleMessage = useLastCallback((scheduledAt: number) => {
|
const handleRescheduleMessage = useLastCallback((
|
||||||
|
scheduledAt: number,
|
||||||
|
scheduleRepeatPeriod?: number,
|
||||||
|
) => {
|
||||||
rescheduleMessage({
|
rescheduleMessage({
|
||||||
chatId: message.chatId,
|
chatId: message.chatId,
|
||||||
messageId: message.id,
|
messageId: message.id,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
});
|
});
|
||||||
onClose();
|
onClose();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import type {
|
|||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { formatDateTimeToString, formatPastTimeShort, formatTime } from '../../../util/dates/dateFormat';
|
import { formatDateTimeToString, formatPastTimeShort, formatTime } from '../../../util/dates/dateFormat';
|
||||||
import { formatStarsAsIcon } from '../../../util/localization/format';
|
import { formatStarsAsIcon } from '../../../util/localization/format';
|
||||||
|
import { getRepeatPeriodText } from '../../../util/scheduledMessages';
|
||||||
import { formatIntegerCompact } from '../../../util/textFormat';
|
import { formatIntegerCompact } from '../../../util/textFormat';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
|
||||||
@ -83,6 +84,10 @@ const MessageMeta: FC<OwnProps> = ({
|
|||||||
onOpenThread();
|
onOpenThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const repeatPeriodText = useMemo(() => {
|
||||||
|
return getRepeatPeriodText(message.scheduleRepeatPeriod, lang);
|
||||||
|
}, [message.scheduleRepeatPeriod, lang]);
|
||||||
|
|
||||||
const dateTitle = useMemo(() => {
|
const dateTitle = useMemo(() => {
|
||||||
if (!isActivated) return undefined;
|
if (!isActivated) return undefined;
|
||||||
const createDateTime = formatDateTimeToString(message.date * 1000, oldLang.code, undefined, oldLang.timeFormat);
|
const createDateTime = formatDateTimeToString(message.date * 1000, oldLang.code, undefined, oldLang.timeFormat);
|
||||||
@ -134,12 +139,16 @@ const MessageMeta: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const date = useMemo(() => {
|
const date = useMemo(() => {
|
||||||
const time = formatTime(oldLang, message.date * 1000);
|
const time = formatTime(oldLang, message.date * 1000);
|
||||||
if (!withFullDate) {
|
const baseDate = !withFullDate
|
||||||
return time;
|
? time
|
||||||
|
: formatPastTimeShort(oldLang, (message.forwardInfo?.date || message.date) * 1000, true);
|
||||||
|
|
||||||
|
if (repeatPeriodText) {
|
||||||
|
return lang('FormatDateAtTime', { date: repeatPeriodText, time: baseDate });
|
||||||
}
|
}
|
||||||
|
|
||||||
return formatPastTimeShort(oldLang, (message.forwardInfo?.date || message.date) * 1000, true);
|
return baseDate;
|
||||||
}, [oldLang, message.date, message.forwardInfo?.date, withFullDate]);
|
}, [oldLang, message.date, message.forwardInfo?.date, withFullDate, repeatPeriodText, lang]);
|
||||||
|
|
||||||
const fullClassName = buildClassName(
|
const fullClassName = buildClassName(
|
||||||
'MessageMeta',
|
'MessageMeta',
|
||||||
|
|||||||
@ -87,11 +87,12 @@ const GifSearch: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (shouldSchedule) {
|
if (shouldSchedule) {
|
||||||
requestCalendar((scheduledAt) => {
|
requestCalendar((scheduledAt, scheduleRepeatPeriod) => {
|
||||||
sendMessage({
|
sendMessage({
|
||||||
messageList: currentMessageList,
|
messageList: currentMessageList,
|
||||||
gif,
|
gif,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
isSilent,
|
isSilent,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1545,7 +1545,7 @@ addActionHandler('sendScheduledMessages', (global, actions, payload): ActionRetu
|
|||||||
|
|
||||||
addActionHandler('rescheduleMessage', (global, actions, payload): ActionReturnType => {
|
addActionHandler('rescheduleMessage', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
chatId, messageId, scheduledAt,
|
chatId, messageId, scheduledAt, scheduleRepeatPeriod,
|
||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
@ -1558,6 +1558,7 @@ addActionHandler('rescheduleMessage', (global, actions, payload): ActionReturnTy
|
|||||||
chat,
|
chat,
|
||||||
message,
|
message,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1610,19 +1611,19 @@ addActionHandler('loadCustomEmojis', async (global, actions, payload): Promise<v
|
|||||||
|
|
||||||
addActionHandler('forwardMessages', (global, actions, payload): ActionReturnType => {
|
addActionHandler('forwardMessages', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
isSilent, scheduledAt, tabId = getCurrentTabId(),
|
isSilent, scheduledAt, scheduleRepeatPeriod, tabId = getCurrentTabId(),
|
||||||
} = payload;
|
} = payload;
|
||||||
const { toChatId } = selectTabState(global, tabId).forwardMessages;
|
const { toChatId } = selectTabState(global, tabId).forwardMessages;
|
||||||
const toChat = toChatId ? selectChat(global, toChatId) : undefined;
|
const toChat = toChatId ? selectChat(global, toChatId) : undefined;
|
||||||
if (!toChat) return;
|
if (!toChat) return;
|
||||||
executeForwardMessages(global, { chat: toChat, isSilent, scheduledAt }, tabId);
|
executeForwardMessages(global, { chat: toChat, isSilent, scheduledAt, scheduleRepeatPeriod }, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
async function executeForwardMessages(global: GlobalState, sendParams: SendMessageParams, tabId: number) {
|
async function executeForwardMessages(global: GlobalState, sendParams: SendMessageParams, tabId: number) {
|
||||||
const {
|
const {
|
||||||
fromChatId, messageIds, toChatId, withMyScore, noAuthors, noCaptions, toThreadId = MAIN_THREAD_ID,
|
fromChatId, messageIds, toChatId, withMyScore, noAuthors, noCaptions, toThreadId = MAIN_THREAD_ID,
|
||||||
} = selectTabState(global, tabId).forwardMessages;
|
} = selectTabState(global, tabId).forwardMessages;
|
||||||
const { messagePriceInStars, isSilent, scheduledAt } = sendParams;
|
const { messagePriceInStars, isSilent, scheduledAt, scheduleRepeatPeriod } = sendParams;
|
||||||
|
|
||||||
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
const isCurrentUserPremium = selectIsCurrentUserPremium(global);
|
||||||
const isToMainThread = toThreadId === MAIN_THREAD_ID;
|
const isToMainThread = toThreadId === MAIN_THREAD_ID;
|
||||||
@ -1659,6 +1660,7 @@ async function executeForwardMessages(global: GlobalState, sendParams: SendMessa
|
|||||||
messages: slice,
|
messages: slice,
|
||||||
isSilent,
|
isSilent,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
sendAs,
|
sendAs,
|
||||||
withMyScore,
|
withMyScore,
|
||||||
noAuthors,
|
noAuthors,
|
||||||
@ -1696,6 +1698,7 @@ async function executeForwardMessages(global: GlobalState, sendParams: SendMessa
|
|||||||
sticker,
|
sticker,
|
||||||
isSilent,
|
isSilent,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
|
scheduleRepeatPeriod,
|
||||||
sendAs,
|
sendAs,
|
||||||
lastMessageId,
|
lastMessageId,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -461,6 +461,7 @@ export interface ActionPayloads {
|
|||||||
chatId: string;
|
chatId: string;
|
||||||
messageId: number;
|
messageId: number;
|
||||||
scheduledAt: number;
|
scheduledAt: number;
|
||||||
|
scheduleRepeatPeriod?: number;
|
||||||
};
|
};
|
||||||
deleteScheduledMessages: { messageIds: number[] } & WithTabId;
|
deleteScheduledMessages: { messageIds: number[] } & WithTabId;
|
||||||
// Message
|
// Message
|
||||||
@ -1934,6 +1935,7 @@ export interface ActionPayloads {
|
|||||||
forwardMessages: {
|
forwardMessages: {
|
||||||
isSilent?: boolean;
|
isSilent?: boolean;
|
||||||
scheduledAt?: number;
|
scheduledAt?: number;
|
||||||
|
scheduleRepeatPeriod?: number;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
setForwardNoAuthors: {
|
setForwardNoAuthors: {
|
||||||
noAuthors: boolean;
|
noAuthors: boolean;
|
||||||
|
|||||||
@ -1,33 +1,39 @@
|
|||||||
import { useState } from '../lib/teact/teact';
|
import { useState } from '../lib/teact/teact';
|
||||||
|
|
||||||
|
import type { RepeatedMessageMode } from '../util/scheduledMessages';
|
||||||
|
|
||||||
import { SCHEDULED_WHEN_ONLINE } from '../config';
|
import { SCHEDULED_WHEN_ONLINE } from '../config';
|
||||||
import { getDayStartAt } from '../util/dates/dateFormat';
|
import { getDayStartAt } from '../util/dates/dateFormat';
|
||||||
|
import { getRepeatModeFromSeconds, getRepeatPeriodSeconds } from '../util/scheduledMessages';
|
||||||
import { getServerTimeOffset } from '../util/serverTime';
|
import { getServerTimeOffset } from '../util/serverTime';
|
||||||
import useLastCallback from './useLastCallback';
|
import useLastCallback from './useLastCallback';
|
||||||
import useOldLang from './useOldLang';
|
import useOldLang from './useOldLang';
|
||||||
|
|
||||||
import CalendarModal from '../components/common/CalendarModal.async';
|
import CalendarModal from '../components/common/CalendarModal.async';
|
||||||
|
|
||||||
type OnScheduledCallback = (scheduledAt: number) => void;
|
type OnScheduledCallback = (scheduledAt: number, repeatPeriod?: number) => void;
|
||||||
|
|
||||||
const useSchedule = (
|
const useSchedule = (
|
||||||
canScheduleUntilOnline?: boolean,
|
canScheduleUntilOnline?: boolean,
|
||||||
onCancel?: () => void,
|
onCancel?: () => void,
|
||||||
openAt?: number,
|
openAt?: number,
|
||||||
|
initialRepeatPeriod?: number,
|
||||||
) => {
|
) => {
|
||||||
const lang = useOldLang();
|
const lang = useOldLang();
|
||||||
const [onScheduled, setOnScheduled] = useState<OnScheduledCallback | undefined>();
|
const [onScheduled, setOnScheduled] = useState<OnScheduledCallback | undefined>();
|
||||||
|
|
||||||
const handleMessageSchedule = useLastCallback((date: Date, isWhenOnline = false) => {
|
const handleMessageSchedule = useLastCallback((date: Date, repeatMode?: RepeatedMessageMode) => {
|
||||||
// Scheduled time can not be less than 10 seconds in future
|
// Scheduled time can not be less than 10 seconds in future
|
||||||
|
const isWhenOnline = date.getTime() === SCHEDULED_WHEN_ONLINE * 1000;
|
||||||
const scheduledAt = Math.round(Math.max(date.getTime(), Date.now() + 60 * 1000) / 1000)
|
const scheduledAt = Math.round(Math.max(date.getTime(), Date.now() + 60 * 1000) / 1000)
|
||||||
+ (isWhenOnline ? 0 : getServerTimeOffset());
|
+ (isWhenOnline ? 0 : getServerTimeOffset());
|
||||||
onScheduled?.(scheduledAt);
|
const repeatPeriod = getRepeatPeriodSeconds(repeatMode);
|
||||||
|
onScheduled?.(scheduledAt, repeatPeriod);
|
||||||
setOnScheduled(undefined);
|
setOnScheduled(undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleMessageScheduleUntilOnline = useLastCallback(() => {
|
const handleMessageScheduleUntilOnline = useLastCallback(() => {
|
||||||
handleMessageSchedule(new Date(SCHEDULED_WHEN_ONLINE * 1000), true);
|
handleMessageSchedule(new Date(SCHEDULED_WHEN_ONLINE * 1000));
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleCloseCalendar = useLastCallback(() => {
|
const handleCloseCalendar = useLastCallback(() => {
|
||||||
@ -46,10 +52,14 @@ const useSchedule = (
|
|||||||
const scheduledMaxDate = new Date();
|
const scheduledMaxDate = new Date();
|
||||||
scheduledMaxDate.setFullYear(scheduledMaxDate.getFullYear() + 1);
|
scheduledMaxDate.setFullYear(scheduledMaxDate.getFullYear() + 1);
|
||||||
|
|
||||||
|
const initialRepeatMode = getRepeatModeFromSeconds(initialRepeatPeriod);
|
||||||
|
|
||||||
const calendar = (
|
const calendar = (
|
||||||
<CalendarModal
|
<CalendarModal
|
||||||
isOpen={Boolean(onScheduled)}
|
isOpen={Boolean(onScheduled)}
|
||||||
withTimePicker
|
withTimePicker
|
||||||
|
withRepeatMode
|
||||||
|
initialRepeatMode={initialRepeatMode}
|
||||||
selectedAt={scheduledDefaultDate.getTime()}
|
selectedAt={scheduledDefaultDate.getTime()}
|
||||||
maxAt={getDayStartAt(scheduledMaxDate)}
|
maxAt={getDayStartAt(scheduledMaxDate)}
|
||||||
isFutureMode
|
isFutureMode
|
||||||
|
|||||||
@ -723,6 +723,7 @@ export type SendMessageParams = {
|
|||||||
contact?: ApiContact;
|
contact?: ApiContact;
|
||||||
isSilent?: boolean;
|
isSilent?: boolean;
|
||||||
scheduledAt?: number;
|
scheduledAt?: number;
|
||||||
|
scheduleRepeatPeriod?: number;
|
||||||
groupedId?: string;
|
groupedId?: string;
|
||||||
noWebPage?: boolean;
|
noWebPage?: boolean;
|
||||||
sendAs?: ApiPeer;
|
sendAs?: ApiPeer;
|
||||||
@ -758,6 +759,7 @@ export type ForwardMessagesParams = {
|
|||||||
messages: ApiMessage[];
|
messages: ApiMessage[];
|
||||||
isSilent?: boolean;
|
isSilent?: boolean;
|
||||||
scheduledAt?: number;
|
scheduledAt?: number;
|
||||||
|
scheduleRepeatPeriod?: number;
|
||||||
sendAs?: ApiPeer;
|
sendAs?: ApiPeer;
|
||||||
withMyScore?: boolean;
|
withMyScore?: boolean;
|
||||||
noAuthors?: boolean;
|
noAuthors?: boolean;
|
||||||
|
|||||||
27
src/types/language.d.ts
vendored
27
src/types/language.d.ts
vendored
@ -1006,6 +1006,12 @@ export interface LangPair {
|
|||||||
'WeekdayYesterday': undefined;
|
'WeekdayYesterday': undefined;
|
||||||
'User': undefined;
|
'User': undefined;
|
||||||
'SecretChat': undefined;
|
'SecretChat': undefined;
|
||||||
|
'MessageRepeatPeriodDaily': undefined;
|
||||||
|
'MessageRepeatPeriodWeekly': undefined;
|
||||||
|
'MessageRepeatPeriodBiweekly': undefined;
|
||||||
|
'MessageRepeatPeriodMonthly': undefined;
|
||||||
|
'MessageRepeatPeriodYearly': undefined;
|
||||||
|
'MessageScheduledRepeatPremium': undefined;
|
||||||
'ChatListFilterErrorEmpty': undefined;
|
'ChatListFilterErrorEmpty': undefined;
|
||||||
'ChatListFilterErrorTitleEmpty': undefined;
|
'ChatListFilterErrorTitleEmpty': undefined;
|
||||||
'FilterMuted': undefined;
|
'FilterMuted': undefined;
|
||||||
@ -1052,6 +1058,12 @@ export interface LangPair {
|
|||||||
'Archive': undefined;
|
'Archive': undefined;
|
||||||
'WaitingForNetwork': undefined;
|
'WaitingForNetwork': undefined;
|
||||||
'ScheduleSendWhenOnline': undefined;
|
'ScheduleSendWhenOnline': undefined;
|
||||||
|
'ScheduleRepeatNever': undefined;
|
||||||
|
'ScheduleRepeatDaily': undefined;
|
||||||
|
'ScheduleRepeatWeekly': undefined;
|
||||||
|
'ScheduleRepeatBiweekly': undefined;
|
||||||
|
'ScheduleRepeatMonthly': undefined;
|
||||||
|
'ScheduleRepeatYearly': undefined;
|
||||||
'VoipIncoming': undefined;
|
'VoipIncoming': undefined;
|
||||||
'LiveLocationUpdatedJustNow': undefined;
|
'LiveLocationUpdatedJustNow': undefined;
|
||||||
'RightNow': undefined;
|
'RightNow': undefined;
|
||||||
@ -2057,6 +2069,9 @@ export interface LangPairWithVariables<V = LangVariable> {
|
|||||||
'count': V;
|
'count': V;
|
||||||
'total': V;
|
'total': V;
|
||||||
};
|
};
|
||||||
|
'ScheduleRepeat': {
|
||||||
|
'value': V;
|
||||||
|
};
|
||||||
'MessageTimerShortHours': {
|
'MessageTimerShortHours': {
|
||||||
'count': V;
|
'count': V;
|
||||||
};
|
};
|
||||||
@ -3182,12 +3197,24 @@ export interface LangPairPluralWithVariables<V = LangVariable> {
|
|||||||
'LastSeenHoursAgo': {
|
'LastSeenHoursAgo': {
|
||||||
'count': V;
|
'count': V;
|
||||||
};
|
};
|
||||||
|
'MessageRepeatPeriodEveryMinutes': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
|
'MessageRepeatPeriodEveryMonths': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
'StickerPackRemoveStickerCount': {
|
'StickerPackRemoveStickerCount': {
|
||||||
'count': V;
|
'count': V;
|
||||||
};
|
};
|
||||||
'StickerPackAddStickerCount': {
|
'StickerPackAddStickerCount': {
|
||||||
'count': V;
|
'count': V;
|
||||||
};
|
};
|
||||||
|
'ScheduleRepeatEveryMinutes': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
|
'ScheduleRepeatEveryMonths': {
|
||||||
|
'count': V;
|
||||||
|
};
|
||||||
'Years': {
|
'Years': {
|
||||||
'count': V;
|
'count': V;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,6 +2,8 @@
|
|||||||
export const MINUTE = 60;
|
export const MINUTE = 60;
|
||||||
export const HOUR = 3600;
|
export const HOUR = 3600;
|
||||||
export const DAY = 86400;
|
export const DAY = 86400;
|
||||||
|
export const WEEK = 7 * DAY;
|
||||||
|
export const MONTH = 30 * DAY;
|
||||||
|
|
||||||
export function getMinutes(seconds: number, roundDown?: boolean) {
|
export function getMinutes(seconds: number, roundDown?: boolean) {
|
||||||
const roundFunc = roundDown ? Math.floor : Math.ceil;
|
const roundFunc = roundDown ? Math.floor : Math.ceil;
|
||||||
|
|||||||
100
src/util/scheduledMessages.ts
Normal file
100
src/util/scheduledMessages.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import type { LangFn } from './localization';
|
||||||
|
|
||||||
|
import { DAY, MINUTE, MONTH, WEEK } from './dates/units';
|
||||||
|
|
||||||
|
export type RepeatedMessageMode = 'never' | 'everyminute' | 'every5minutes' | 'daily' | 'weekly'
|
||||||
|
| 'biweekly' | 'monthly' | 'every3months' | 'every6months' | 'yearly';
|
||||||
|
|
||||||
|
type ActualRepeatedMessageMode = Exclude<RepeatedMessageMode, 'never'>;
|
||||||
|
|
||||||
|
const MODE_TO_SECONDS: Record<ActualRepeatedMessageMode, number> = {
|
||||||
|
everyminute: MINUTE,
|
||||||
|
every5minutes: 5 * MINUTE,
|
||||||
|
daily: DAY,
|
||||||
|
weekly: WEEK,
|
||||||
|
biweekly: 2 * WEEK,
|
||||||
|
monthly: MONTH,
|
||||||
|
every3months: 91 * DAY,
|
||||||
|
every6months: 182 * DAY,
|
||||||
|
yearly: 365 * DAY,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ALL_REPEAT_MODES: RepeatedMessageMode[] = [
|
||||||
|
'never',
|
||||||
|
...Object.keys(MODE_TO_SECONDS) as ActualRepeatedMessageMode[],
|
||||||
|
];
|
||||||
|
|
||||||
|
export const TEST_SERVER_ONLY_MODES = new Set<RepeatedMessageMode>([
|
||||||
|
'everyminute',
|
||||||
|
'every5minutes',
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function getRepeatPeriodSeconds(mode?: RepeatedMessageMode) {
|
||||||
|
if (!mode || mode === 'never') return undefined;
|
||||||
|
|
||||||
|
return MODE_TO_SECONDS[mode];
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRepeatModeFromSeconds(seconds?: number) {
|
||||||
|
if (!seconds) return undefined;
|
||||||
|
|
||||||
|
return (Object.keys(MODE_TO_SECONDS) as ActualRepeatedMessageMode[])
|
||||||
|
.find((mode) => MODE_TO_SECONDS[mode] === seconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRepeatPeriodText(seconds: number | undefined, lang: LangFn) {
|
||||||
|
if (!seconds) return undefined;
|
||||||
|
|
||||||
|
const mode = getRepeatModeFromSeconds(seconds);
|
||||||
|
if (!mode) return undefined;
|
||||||
|
|
||||||
|
switch (mode) {
|
||||||
|
case 'everyminute':
|
||||||
|
return lang('MessageRepeatPeriodEveryMinutes', { count: 1 }, { pluralValue: 1 });
|
||||||
|
case 'every5minutes':
|
||||||
|
return lang('MessageRepeatPeriodEveryMinutes', { count: 5 }, { pluralValue: 5 });
|
||||||
|
case 'daily':
|
||||||
|
return lang('MessageRepeatPeriodDaily');
|
||||||
|
case 'weekly':
|
||||||
|
return lang('MessageRepeatPeriodWeekly');
|
||||||
|
case 'biweekly':
|
||||||
|
return lang('MessageRepeatPeriodBiweekly');
|
||||||
|
case 'monthly':
|
||||||
|
return lang('MessageRepeatPeriodMonthly');
|
||||||
|
case 'every3months':
|
||||||
|
return lang('MessageRepeatPeriodEveryMonths', { count: 3 }, { pluralValue: 3 });
|
||||||
|
case 'every6months':
|
||||||
|
return lang('MessageRepeatPeriodEveryMonths', { count: 6 }, { pluralValue: 6 });
|
||||||
|
case 'yearly':
|
||||||
|
return lang('MessageRepeatPeriodYearly');
|
||||||
|
default:
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getScheduleRepeatModeText(mode: RepeatedMessageMode, lang: LangFn) {
|
||||||
|
switch (mode) {
|
||||||
|
case 'never':
|
||||||
|
return lang('ScheduleRepeatNever');
|
||||||
|
case 'everyminute':
|
||||||
|
return lang('ScheduleRepeatEveryMinutes', { count: 1 }, { pluralValue: 1 });
|
||||||
|
case 'every5minutes':
|
||||||
|
return lang('ScheduleRepeatEveryMinutes', { count: 5 }, { pluralValue: 5 });
|
||||||
|
case 'daily':
|
||||||
|
return lang('ScheduleRepeatDaily');
|
||||||
|
case 'weekly':
|
||||||
|
return lang('ScheduleRepeatWeekly');
|
||||||
|
case 'biweekly':
|
||||||
|
return lang('ScheduleRepeatBiweekly');
|
||||||
|
case 'monthly':
|
||||||
|
return lang('ScheduleRepeatMonthly');
|
||||||
|
case 'every3months':
|
||||||
|
return lang('ScheduleRepeatEveryMonths', { count: 3 }, { pluralValue: 3 });
|
||||||
|
case 'every6months':
|
||||||
|
return lang('ScheduleRepeatEveryMonths', { count: 6 }, { pluralValue: 6 });
|
||||||
|
case 'yearly':
|
||||||
|
return lang('ScheduleRepeatYearly');
|
||||||
|
default:
|
||||||
|
return lang('ScheduleRepeatNever');
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user