Layer 205: Introduce Checklists (#6010)
Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com>
This commit is contained in:
parent
bcaeee1d94
commit
9bfd2d569c
@ -9,6 +9,9 @@ import {
|
||||
SERVICE_NOTIFICATIONS_USER_ID,
|
||||
STORY_EXPIRE_PERIOD,
|
||||
STORY_VIEWERS_EXPIRE_PERIOD,
|
||||
TODO_ITEM_LENGTH_LIMIT,
|
||||
TODO_ITEMS_LIMIT,
|
||||
TODO_TITLE_LENGTH_LIMIT,
|
||||
} from '../../../config';
|
||||
import localDb from '../localDb';
|
||||
import { buildJson } from './misc';
|
||||
@ -99,6 +102,9 @@ export interface GramJsAppConfig extends LimitsConfig {
|
||||
stars_stargift_resale_amount_min?: number;
|
||||
stars_stargift_resale_commission_permille?: number;
|
||||
poll_answers_max?: number;
|
||||
todo_items_max?: number;
|
||||
todo_title_length_max?: number;
|
||||
todo_item_length_max?: number;
|
||||
}
|
||||
|
||||
function buildEmojiSounds(appConfig: GramJsAppConfig) {
|
||||
@ -200,5 +206,8 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
||||
starsStargiftResaleAmountMax: appConfig.stars_stargift_resale_amount_max,
|
||||
starsStargiftResaleCommissionPermille: appConfig.stars_stargift_resale_commission_permille,
|
||||
pollMaxAnswers: appConfig.poll_answers_max,
|
||||
todoItemsMax: appConfig.todo_items_max ?? TODO_ITEMS_LIMIT,
|
||||
todoTitleLengthMax: appConfig.todo_title_length_max ?? TODO_TITLE_LENGTH_LIMIT,
|
||||
todoItemLengthMax: appConfig.todo_item_length_max ?? TODO_ITEM_LENGTH_LIMIT,
|
||||
};
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import type { ApiMessageAction } from '../../types/messageActions';
|
||||
import { buildApiBotApp } from './bots';
|
||||
import { buildApiFormattedText, buildApiPhoto } from './common';
|
||||
import { buildApiStarGift } from './gifts';
|
||||
import { buildTodoItem } from './messageContent';
|
||||
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
||||
|
||||
const UNSUPPORTED_ACTION: ApiMessageAction = {
|
||||
@ -446,6 +447,25 @@ export function buildApiMessageAction(action: GramJs.TypeMessageAction): ApiMess
|
||||
count,
|
||||
};
|
||||
}
|
||||
if (action instanceof GramJs.MessageActionTodoCompletions) {
|
||||
const {
|
||||
completed, incompleted,
|
||||
} = action;
|
||||
return {
|
||||
mediaType: 'action',
|
||||
type: 'todoCompletions',
|
||||
completedIds: completed,
|
||||
incompletedIds: incompleted,
|
||||
};
|
||||
}
|
||||
if (action instanceof GramJs.MessageActionTodoAppendTasks) {
|
||||
const { list } = action;
|
||||
return {
|
||||
mediaType: 'action',
|
||||
type: 'todoAppendTasks',
|
||||
items: list.map(buildTodoItem),
|
||||
};
|
||||
}
|
||||
|
||||
return UNSUPPORTED_ACTION;
|
||||
}
|
||||
|
||||
@ -11,12 +11,14 @@ import type {
|
||||
ApiLocation,
|
||||
ApiMediaExtendedPreview,
|
||||
ApiMediaInvoice,
|
||||
ApiMediaTodo,
|
||||
ApiMessageStoryData,
|
||||
ApiPaidMedia,
|
||||
ApiPhoto,
|
||||
ApiPoll,
|
||||
ApiStarGiftUnique,
|
||||
ApiSticker,
|
||||
ApiTodoItem,
|
||||
ApiVideo,
|
||||
ApiVoice,
|
||||
ApiWebDocument,
|
||||
@ -64,7 +66,7 @@ export function buildMessageContent(
|
||||
const hasUnsupportedMedia = mtpMessage.media instanceof GramJs.MessageMediaUnsupported;
|
||||
|
||||
if (mtpMessage.message && !hasUnsupportedMedia
|
||||
&& !content.sticker && !content.pollId && !content.contact && !content.video?.isRound) {
|
||||
&& !content.sticker && !content.pollId && !content.todo && !content.contact && !content.video?.isRound) {
|
||||
const text = buildMessageTextContent(mtpMessage.message, mtpMessage.entities);
|
||||
const textWithTimestamps = addTimestampEntities(text);
|
||||
content = {
|
||||
@ -153,6 +155,9 @@ export function buildMessageMediaContent(
|
||||
const pollId = buildPollIdFromMedia(media);
|
||||
if (pollId) return { pollId };
|
||||
|
||||
const todo = buildTodoFromMedia(media);
|
||||
if (todo) return { todo };
|
||||
|
||||
const webPage = buildWebPage(media);
|
||||
if (webPage) return { webPage };
|
||||
|
||||
@ -513,6 +518,14 @@ export function buildPollFromMedia(media: GramJs.TypeMessageMedia): ApiPoll | un
|
||||
return buildPoll(media.poll, media.results);
|
||||
}
|
||||
|
||||
function buildTodoFromMedia(media: GramJs.TypeMessageMedia): ApiMediaTodo | undefined {
|
||||
if (!(media instanceof GramJs.MessageMediaToDo)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return buildTodo(media.todo, media.completions);
|
||||
}
|
||||
|
||||
function buildInvoiceFromMedia(media: GramJs.TypeMessageMedia): ApiMediaInvoice | undefined {
|
||||
if (!(media instanceof GramJs.MessageMediaInvoice)) {
|
||||
return undefined;
|
||||
@ -711,6 +724,36 @@ export function buildPoll(poll: GramJs.Poll, pollResults: GramJs.PollResults): A
|
||||
};
|
||||
}
|
||||
|
||||
export function buildTodoItem(item: GramJs.TodoItem): ApiTodoItem {
|
||||
return {
|
||||
id: item.id,
|
||||
title: buildApiFormattedText(item.title),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildTodo(todo: GramJs.TodoList, completions?: GramJs.TodoCompletion[]): ApiMediaTodo {
|
||||
const { title, list: items } = todo;
|
||||
|
||||
const todoItems = items.map(buildTodoItem);
|
||||
|
||||
const todoCompletions = completions?.map((completion) => ({
|
||||
itemId: completion.id,
|
||||
completedBy: completion.completedBy.toString(),
|
||||
completedAt: completion.date,
|
||||
}));
|
||||
|
||||
return {
|
||||
mediaType: 'todo',
|
||||
todo: {
|
||||
title: buildApiFormattedText(title),
|
||||
items: todoItems,
|
||||
othersCanAppend: todo.othersCanAppend,
|
||||
othersCanComplete: todo.othersCanComplete,
|
||||
},
|
||||
completions: todoCompletions,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildMediaInvoice(media: GramJs.MessageMediaInvoice): ApiMediaInvoice {
|
||||
const {
|
||||
description, title, photo, test, totalAmount, currency, receiptMsgId, extendedMedia,
|
||||
|
||||
@ -8,10 +8,12 @@ import type {
|
||||
ApiFactCheck,
|
||||
ApiInputMessageReplyInfo,
|
||||
ApiInputReplyInfo,
|
||||
ApiMediaTodo,
|
||||
ApiMessage,
|
||||
ApiMessageEntity,
|
||||
ApiMessageForwardInfo,
|
||||
ApiMessageReportResult,
|
||||
ApiNewMediaTodo,
|
||||
ApiNewPoll,
|
||||
ApiPeer,
|
||||
ApiPhoto,
|
||||
@ -382,6 +384,13 @@ function buildNewPoll(poll: ApiNewPoll, localId: number): ApiPoll {
|
||||
};
|
||||
}
|
||||
|
||||
function buildNewTodo(todo: ApiNewMediaTodo): ApiMediaTodo {
|
||||
return {
|
||||
mediaType: 'todo',
|
||||
todo: todo.todo,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildLocalMessage(
|
||||
chat: ApiChat,
|
||||
lastMessageId?: number,
|
||||
@ -392,6 +401,7 @@ export function buildLocalMessage(
|
||||
sticker?: ApiSticker,
|
||||
gif?: ApiVideo,
|
||||
poll?: ApiNewPoll,
|
||||
todo?: ApiNewMediaTodo,
|
||||
contact?: ApiContact,
|
||||
groupedId?: string,
|
||||
scheduledAt?: number,
|
||||
@ -409,6 +419,7 @@ export function buildLocalMessage(
|
||||
const resultReplyInfo = replyInfo && buildReplyInfo(replyInfo, chat.isForum);
|
||||
|
||||
const localPoll = poll && buildNewPoll(poll, localId);
|
||||
const localTodo = todo && buildNewTodo(todo);
|
||||
|
||||
const formattedText = text ? addTimestampEntities({ text, entities }) : undefined;
|
||||
|
||||
@ -423,6 +434,7 @@ export function buildLocalMessage(
|
||||
contact,
|
||||
storyData: story && { mediaType: 'storyData', ...story },
|
||||
pollId: localPoll?.id,
|
||||
todo: localTodo,
|
||||
}),
|
||||
date: scheduledAt || Math.round(Date.now() / 1000) + getServerTimeOffset(),
|
||||
isOutgoing: !isChannel,
|
||||
|
||||
@ -16,6 +16,7 @@ import type {
|
||||
ApiInputReplyInfo,
|
||||
ApiInputStorePaymentPurpose,
|
||||
ApiMessageEntity,
|
||||
ApiNewMediaTodo,
|
||||
ApiNewPoll,
|
||||
ApiPhoneCall,
|
||||
ApiPhoto,
|
||||
@ -263,6 +264,28 @@ export function buildInputPollFromExisting(poll: ApiPoll, shouldClose = false) {
|
||||
});
|
||||
}
|
||||
|
||||
export function buildInputTodo(todo: ApiNewMediaTodo) {
|
||||
const { title, items } = todo.todo;
|
||||
|
||||
const todoItems = items.map((item) => {
|
||||
return new GramJs.TodoItem({
|
||||
id: item.id,
|
||||
title: buildInputTextWithEntities(item.title),
|
||||
});
|
||||
});
|
||||
|
||||
const todoList = new GramJs.TodoList({
|
||||
title: buildInputTextWithEntities(title),
|
||||
list: todoItems,
|
||||
othersCanAppend: todo.todo.othersCanAppend || undefined,
|
||||
othersCanComplete: todo.todo.othersCanComplete || undefined,
|
||||
});
|
||||
|
||||
return new GramJs.InputMediaTodo({
|
||||
todo: todoList,
|
||||
});
|
||||
}
|
||||
|
||||
export function buildFilterFromApiFolder(folder: ApiChatFolder): GramJs.DialogFilter | GramJs.DialogFilterChatlist {
|
||||
const {
|
||||
emoticon,
|
||||
|
||||
@ -18,11 +18,13 @@ import type {
|
||||
ApiMessageEntity,
|
||||
ApiMessageSearchContext,
|
||||
ApiMessageSearchType,
|
||||
ApiNewMediaTodo,
|
||||
ApiOnProgress,
|
||||
ApiPeer,
|
||||
ApiPoll,
|
||||
ApiReaction,
|
||||
ApiSendMessageAction,
|
||||
ApiTodoItem,
|
||||
ApiUser,
|
||||
ApiUserStatus,
|
||||
MediaContent,
|
||||
@ -47,7 +49,7 @@ import {
|
||||
import { fetchFile } from '../../../util/files';
|
||||
import { compact, split } from '../../../util/iteratees';
|
||||
import { getMessageKey } from '../../../util/keys/messageKey';
|
||||
import { getServerTime, getServerTimeOffset } from '../../../util/serverTime';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import { interpolateArray } from '../../../util/waveform';
|
||||
import {
|
||||
buildApiChatFromPreview,
|
||||
@ -82,6 +84,7 @@ import {
|
||||
buildInputReplyTo,
|
||||
buildInputStory,
|
||||
buildInputTextWithEntities,
|
||||
buildInputTodo,
|
||||
buildInputUser,
|
||||
buildMessageFromUpdate,
|
||||
buildMtpMessageEntity,
|
||||
@ -263,7 +266,7 @@ export function sendMessageLocal(
|
||||
params: SendMessageParams,
|
||||
) {
|
||||
const {
|
||||
chat, lastMessageId, text, entities, replyInfo, attachment, sticker, story, gif, poll, contact,
|
||||
chat, lastMessageId, text, entities, replyInfo, attachment, sticker, story, gif, poll, todo, contact,
|
||||
scheduledAt, groupedId, sendAs, wasDrafted, isInvertedMedia, effectId, isPending, messagePriceInStars,
|
||||
} = params;
|
||||
|
||||
@ -282,6 +285,7 @@ export function sendMessageLocal(
|
||||
sticker,
|
||||
gif,
|
||||
poll,
|
||||
todo,
|
||||
contact,
|
||||
groupedId,
|
||||
scheduledAt,
|
||||
@ -311,7 +315,7 @@ export function sendApiMessage(
|
||||
onProgress?: ApiOnProgress,
|
||||
) {
|
||||
const {
|
||||
chat, text, entities, replyInfo, attachment, sticker, story, gif, poll, contact,
|
||||
chat, text, entities, replyInfo, attachment, sticker, story, gif, poll, todo, contact,
|
||||
isSilent, scheduledAt, groupedId, noWebPage, sendAs, shouldUpdateStickerSetOrder,
|
||||
isInvertedMedia, effectId, webPageMediaSize, webPageUrl, messagePriceInStars,
|
||||
} = params;
|
||||
@ -368,6 +372,8 @@ export function sendApiMessage(
|
||||
media = buildInputMediaDocument(gif);
|
||||
} else if (poll) {
|
||||
media = buildInputPoll(poll, randomId);
|
||||
} else if (todo) {
|
||||
media = buildInputTodo(todo);
|
||||
} else if (story) {
|
||||
media = buildInputStory(story);
|
||||
} else if (webPageUrl && webPageMediaSize) {
|
||||
@ -629,7 +635,7 @@ export async function editMessage({
|
||||
attachment?: ApiAttachment;
|
||||
noWebPage?: boolean;
|
||||
}, onProgress?: ApiOnProgress) {
|
||||
const isScheduled = message.date * 1000 > Date.now() + getServerTimeOffset() * 1000;
|
||||
const isScheduled = message.date * 1000 > getServerTime() * 1000;
|
||||
|
||||
const media = attachment && buildUploadingMedia(attachment);
|
||||
|
||||
@ -702,6 +708,110 @@ export async function editMessage({
|
||||
}
|
||||
}
|
||||
|
||||
export async function editTodo({
|
||||
chat,
|
||||
message,
|
||||
todo,
|
||||
}: {
|
||||
chat: ApiChat;
|
||||
message: ApiMessage;
|
||||
todo: ApiNewMediaTodo;
|
||||
}) {
|
||||
const media = buildInputTodo(todo);
|
||||
const isScheduled = message.date * 1000 > getServerTime() * 1000;
|
||||
|
||||
const newContent: MediaContent = {
|
||||
...message.content,
|
||||
todo: {
|
||||
mediaType: 'todo',
|
||||
todo: todo.todo,
|
||||
},
|
||||
};
|
||||
|
||||
const messageUpdate: Partial<ApiMessage> = {
|
||||
...message,
|
||||
content: newContent,
|
||||
};
|
||||
|
||||
sendApiUpdate({
|
||||
'@type': isScheduled ? 'updateScheduledMessage' : 'updateMessage',
|
||||
id: message.id,
|
||||
chatId: chat.id,
|
||||
message: messageUpdate,
|
||||
});
|
||||
|
||||
try {
|
||||
await invokeRequest(new GramJs.messages.EditMessage({
|
||||
media,
|
||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||
id: message.id,
|
||||
}), { shouldThrow: true });
|
||||
} catch (err) {
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(err);
|
||||
}
|
||||
|
||||
const { message: messageErr } = err as Error;
|
||||
|
||||
sendApiUpdate({
|
||||
'@type': 'error',
|
||||
error: {
|
||||
message: messageErr,
|
||||
hasErrorKey: true,
|
||||
},
|
||||
});
|
||||
|
||||
// Rollback changes
|
||||
sendApiUpdate({
|
||||
'@type': 'updateMessage',
|
||||
id: message.id,
|
||||
chatId: chat.id,
|
||||
message,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function appendTodoList({
|
||||
chat,
|
||||
message,
|
||||
items,
|
||||
}: {
|
||||
chat: ApiChat;
|
||||
message: ApiMessage;
|
||||
items: ApiTodoItem[];
|
||||
}) {
|
||||
const todoItems = items.map((item) => {
|
||||
return new GramJs.TodoItem({
|
||||
id: item.id,
|
||||
title: buildInputTextWithEntities(item.title),
|
||||
});
|
||||
});
|
||||
|
||||
try {
|
||||
await invokeRequest(new GramJs.messages.AppendTodoList({
|
||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||
msgId: message.id,
|
||||
list: todoItems,
|
||||
}), { shouldThrow: true });
|
||||
} catch (err) {
|
||||
if (DEBUG) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(err);
|
||||
}
|
||||
|
||||
const { message: messageErr } = err as Error;
|
||||
|
||||
sendApiUpdate({
|
||||
'@type': 'error',
|
||||
error: {
|
||||
message: messageErr,
|
||||
hasErrorKey: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export async function rescheduleMessage({
|
||||
chat,
|
||||
message,
|
||||
@ -1507,6 +1617,24 @@ export async function sendPollVote({
|
||||
}));
|
||||
}
|
||||
|
||||
export async function toggleTodoCompleted({
|
||||
chat, messageId, completedIds, incompletedIds,
|
||||
}: {
|
||||
chat: ApiChat;
|
||||
messageId: number;
|
||||
completedIds: number[];
|
||||
incompletedIds: number[];
|
||||
}) {
|
||||
const { id, accessHash } = chat;
|
||||
|
||||
await invokeRequest(new GramJs.messages.ToggleTodoCompleted({
|
||||
peer: buildInputPeer(id, accessHash),
|
||||
msgId: messageId,
|
||||
completed: completedIds,
|
||||
incompleted: incompletedIds,
|
||||
}));
|
||||
}
|
||||
|
||||
export async function closePoll({
|
||||
chat, messageId, poll,
|
||||
}: {
|
||||
|
||||
@ -252,11 +252,11 @@ export async function deleteContact({
|
||||
});
|
||||
}
|
||||
|
||||
export async function addNoPaidMessagesException({ user, shouldRefundCharged }: {
|
||||
export async function toggleNoPaidMessagesException({ user, shouldRefundCharged }: {
|
||||
user: ApiUser;
|
||||
shouldRefundCharged?: boolean;
|
||||
}) {
|
||||
const result = await invokeRequest(new GramJs.account.AddNoPaidMessagesException({
|
||||
const result = await invokeRequest(new GramJs.account.ToggleNoPaidMessagesException ({
|
||||
refundCharged: shouldRefundCharged ? true : undefined,
|
||||
userId: buildInputUser(user.id, user.accessHash),
|
||||
}));
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { ApiGroupCall, ApiPhoneCallDiscardReason } from './calls';
|
||||
import type { ApiBotApp, ApiFormattedText, ApiPhoto } from './messages';
|
||||
import type { ApiTodoItem } from './messages';
|
||||
import type { ApiStarGiftRegular, ApiStarGiftUnique } from './stars';
|
||||
|
||||
interface ActionMediaType {
|
||||
@ -281,6 +282,17 @@ export interface ApiMessageActionPaidMessagesPrice extends ActionMediaType {
|
||||
isAllowedInChannel?: boolean;
|
||||
}
|
||||
|
||||
export interface ApiMessageActionTodoCompletions extends ActionMediaType {
|
||||
type: 'todoCompletions';
|
||||
completedIds: number[];
|
||||
incompletedIds: number[];
|
||||
}
|
||||
|
||||
export interface ApiMessageActionTodoAppendTasks extends ActionMediaType {
|
||||
type: 'todoAppendTasks';
|
||||
items: ApiTodoItem[];
|
||||
}
|
||||
|
||||
export interface ApiMessageActionUnsupported extends ActionMediaType {
|
||||
type: 'unsupported';
|
||||
}
|
||||
@ -298,4 +310,5 @@ export type ApiMessageAction = ApiMessageActionUnsupported | ApiMessageActionCha
|
||||
| ApiMessageActionChannelJoined | ApiMessageActionGiftCode | ApiMessageActionGiveawayLaunch
|
||||
| ApiMessageActionGiveawayResults | ApiMessageActionPaymentRefunded | ApiMessageActionGiftStars
|
||||
| ApiMessageActionPrizeStars | ApiMessageActionStarGift | ApiMessageActionStarGiftUnique
|
||||
| ApiMessageActionPaidMessagesRefunded | ApiMessageActionPaidMessagesPrice;
|
||||
| ApiMessageActionPaidMessagesRefunded | ApiMessageActionPaidMessagesPrice | ApiMessageActionTodoCompletions
|
||||
| ApiMessageActionTodoAppendTasks;
|
||||
|
||||
@ -332,6 +332,34 @@ export type ApiNewPoll = {
|
||||
};
|
||||
};
|
||||
|
||||
export interface ApiTodoItem {
|
||||
id: number;
|
||||
title: ApiFormattedText;
|
||||
}
|
||||
|
||||
export interface ApiTodoList {
|
||||
title: ApiFormattedText;
|
||||
items: ApiTodoItem[];
|
||||
othersCanAppend?: boolean;
|
||||
othersCanComplete?: boolean;
|
||||
}
|
||||
|
||||
export interface ApiTodoCompletion {
|
||||
itemId: number;
|
||||
completedBy: string;
|
||||
completedAt: number;
|
||||
}
|
||||
|
||||
export interface ApiMediaTodo {
|
||||
mediaType: 'todo';
|
||||
todo: ApiTodoList;
|
||||
completions?: ApiTodoCompletion[];
|
||||
}
|
||||
|
||||
export type ApiNewMediaTodo = {
|
||||
todo: ApiTodoList;
|
||||
};
|
||||
|
||||
export interface ApiWebPage {
|
||||
mediaType: 'webpage';
|
||||
id: number;
|
||||
@ -516,6 +544,7 @@ export type MediaContent = {
|
||||
sticker?: ApiSticker;
|
||||
contact?: ApiContact;
|
||||
pollId?: string;
|
||||
todo?: ApiMediaTodo;
|
||||
action?: ApiMessageAction;
|
||||
webPage?: ApiWebPage;
|
||||
audio?: ApiAudio;
|
||||
|
||||
@ -253,6 +253,9 @@ export interface ApiAppConfig {
|
||||
starsStargiftResaleAmountMax?: number;
|
||||
starsStargiftResaleCommissionPermille?: number;
|
||||
pollMaxAnswers?: number;
|
||||
todoItemsMax?: number;
|
||||
todoTitleLengthMax?: number;
|
||||
todoItemLengthMax?: number;
|
||||
}
|
||||
|
||||
export interface ApiConfig {
|
||||
|
||||
@ -68,6 +68,7 @@
|
||||
"PremiumPreviewVoiceToTextDescription" = "Ability to read the transcript of any incoming voice message.";
|
||||
"PremiumPreviewProfileBadgeDescription" = "An exclusive badge next to your name showing that you subscribe to Telegram Premium.";
|
||||
"PremiumPreviewDownloadSpeedDescription" = "No more limits on the speed with which media and documents are downloaded.";
|
||||
"PremiumPreviewTodoDescription" = "Plan, assign, and complete tasks - seamlessly and efficiently.";
|
||||
"PremiumPreviewUploadsDescription" = "4 GB per each document, unlimited storage for your chats and media overall.";
|
||||
"PremiumPreviewAdvancedChatManagementDescription" = "Tools to set the default folder, auto-archive and hide new chats from non-contacts.";
|
||||
"PremiumPreviewAnimatedProfilesDescription" = "Video avatars animated in chat lists and chats to allow for additional self-expression.";
|
||||
@ -2024,3 +2025,41 @@
|
||||
"MonoforumComposerPlaceholder" = "Choose a message to reply";
|
||||
"ChannelSendMessage" = "Direct Messages";
|
||||
"AutomaticTranslation" = "Automatic Translation";
|
||||
"TitleNewToDoList" = "New Checklist";
|
||||
"TitleEditToDoList" = "Edit Checklist";
|
||||
"TitleAppendToDoList" = "Append Checklist";
|
||||
"InputTitle" = "Title";
|
||||
"TitleToDoList" = "Checklist";
|
||||
"TitleTask" = "Task";
|
||||
"TitleAddTask" = "Add a task";
|
||||
"AllowOthersAddTasks" = "Allow Others to Add Tasks";
|
||||
"AllowOthersMarkAsDone" = "Allow Others to Mark As Done";
|
||||
"AriaToDoCancel" = "Cancel checklist creation";
|
||||
"TitleGroupToDoList" = "Group Checklist";
|
||||
"TitleUserToDoList" = "{peer}'s Checklist";
|
||||
"TitleYourToDoList" = "Your Checklist";
|
||||
"DescriptionCompletedToDoTasks" = "{number} of {count} completed";
|
||||
"MessageActionTodoCompletionsAsDone" = "{peer} marked \"{task}\" as done";
|
||||
"MessageActionTodoCompletionsAsDoneYou" = "You marked \"{task}\" as done";
|
||||
"MessageActionTodoCompletionsAsDoneMultiple" = "{peer} marked {tasks} as done";
|
||||
"MessageActionTodoCompletionsAsDoneMultipleYou" = "You marked {tasks} as done";
|
||||
"MessageActionTodoCompletionsAsNotDone" = "{peer} marked \"{task}\" as not done";
|
||||
"MessageActionTodoCompletionsAsNotDoneYou" = "You marked \"{task}\" as not done";
|
||||
"MessageActionTodoCompletionsAsNotDoneMultiple" = "{peer} marked {tasks} as not done";
|
||||
"MessageActionTodoCompletionsAsNotDoneMultipleYou" = "You marked {tasks} as not done";
|
||||
"MessageActionTodoTaskCount_one" = "{count} task";
|
||||
"MessageActionTodoTaskCount_other" = "{count} tasks";
|
||||
"ToDoListNewTasks" = "New Tasks";
|
||||
"MenuButtonAppendTodoList" = "Add a Task";
|
||||
"MessageActionAppendTodo" = "{peer} added a new task \"{task}\" to {list}";
|
||||
"MessageActionAppendTodoYou" = "You added a new task \"{task}\" to {list}";
|
||||
"MessageActionAppendTodoMultiple" = "{peer} added {tasks} to {list}";
|
||||
"MessageActionAppendTodoMultipleYou" = "You added {tasks} to {list}";
|
||||
"PremiumMore" = "More";
|
||||
"SubscribeToTelegramPremiumForToggleTask" = "Subscribe to **Telegram Premium** to toggle tasks";
|
||||
"SubscribeToTelegramPremiumForCreateToDo" = "Subscribe to **Telegram Premium** to create Checklists";
|
||||
"SubscribeToTelegramPremiumForAppendToDo" = "Subscribe to **Telegram Premium** to append Checklists";
|
||||
"HintTodoListTasksCount" = "You can add {count} more tasks";
|
||||
"ToDoListErrorChooseTitle" = "Please enter a title.";
|
||||
"ToDoListErrorChooseTasks" = "Please enter at least one task.";
|
||||
"PremiumPreviewTodo" = "Checklists";
|
||||
|
||||
@ -68,6 +68,7 @@ export { default as ReactionPicker } from '../components/middle/message/reaction
|
||||
|
||||
export { default as AttachmentModal } from '../components/middle/composer/AttachmentModal';
|
||||
export { default as PollModal } from '../components/middle/composer/PollModal';
|
||||
export { default as ToDoListModal } from '../components/middle/composer/ToDoListModal';
|
||||
export { default as SymbolMenu } from '../components/middle/composer/SymbolMenu';
|
||||
export { default as ChatCommandTooltip } from '../components/middle/composer/ChatCommandTooltip';
|
||||
export { default as BotCommandMenu } from '../components/middle/composer/BotCommandMenu';
|
||||
|
||||
@ -220,7 +220,7 @@
|
||||
padding: 0;
|
||||
border-radius: 50%;
|
||||
|
||||
font-size: smaller;
|
||||
font-size: 0.875rem;
|
||||
color: white;
|
||||
|
||||
background: var(--color-primary);
|
||||
|
||||
@ -20,6 +20,7 @@ import type {
|
||||
ApiFormattedText,
|
||||
ApiMessage,
|
||||
ApiMessageEntity,
|
||||
ApiNewMediaTodo,
|
||||
ApiNewPoll,
|
||||
ApiPeer,
|
||||
ApiQuickReply,
|
||||
@ -178,6 +179,7 @@ import PollModal from '../middle/composer/PollModal.async';
|
||||
import SendAsMenu from '../middle/composer/SendAsMenu.async';
|
||||
import StickerTooltip from '../middle/composer/StickerTooltip.async';
|
||||
import SymbolMenuButton from '../middle/composer/SymbolMenuButton';
|
||||
import ToDoListModal from '../middle/composer/ToDoListModal.async';
|
||||
import WebPagePreview from '../middle/composer/WebPagePreview';
|
||||
import MessageEffect from '../middle/message/MessageEffect';
|
||||
import ReactionSelector from '../middle/message/reactions/ReactionSelector';
|
||||
@ -235,6 +237,7 @@ type StateProps =
|
||||
isForwarding?: boolean;
|
||||
forwardedMessagesCount?: number;
|
||||
pollModal: TabState['pollModal'];
|
||||
todoListModal: TabState['todoListModal'];
|
||||
botKeyboardMessageId?: number;
|
||||
botKeyboardPlaceholder?: string;
|
||||
withScheduledButton?: boolean;
|
||||
@ -357,6 +360,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
isForwarding,
|
||||
forwardedMessagesCount,
|
||||
pollModal,
|
||||
todoListModal,
|
||||
botKeyboardMessageId,
|
||||
botKeyboardPlaceholder,
|
||||
inputPlaceholder,
|
||||
@ -433,6 +437,8 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
showDialog,
|
||||
openPollModal,
|
||||
closePollModal,
|
||||
openTodoListModal,
|
||||
closeTodoListModal,
|
||||
loadScheduledHistory,
|
||||
openThread,
|
||||
addRecentEmoji,
|
||||
@ -540,7 +546,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
const [nextText, setNextText] = useState<ApiFormattedText | undefined>(undefined);
|
||||
|
||||
const {
|
||||
canSendStickers, canSendGifs, canAttachMedia, canAttachPolls, canAttachEmbedLinks,
|
||||
canSendStickers, canSendGifs, canAttachMedia, canAttachPolls, canAttachEmbedLinks, canAttachToDoLists,
|
||||
canSendVoices, canSendPlainText, canSendAudios, canSendVideos, canSendPhotos, canSendDocuments,
|
||||
} = useMemo(
|
||||
() => getAllowedAttachmentOptions(chat,
|
||||
@ -1216,6 +1222,22 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
handleActionWithPaymentConfirmation(handleSend, isSilent, scheduledAt);
|
||||
});
|
||||
|
||||
const handleTodoListCreate = useLastCallback(() => {
|
||||
if (!isCurrentUserPremium) {
|
||||
showNotification({
|
||||
message: lang('SubscribeToTelegramPremiumForCreateToDo'),
|
||||
action: {
|
||||
action: 'openPremiumModal',
|
||||
payload: { initialSection: 'todo' },
|
||||
},
|
||||
actionText: lang('PremiumMore'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
openTodoListModal({ chatId });
|
||||
});
|
||||
|
||||
const handleClickBotMenu = useLastCallback(() => {
|
||||
if (botMenuButton?.type !== 'webApp') {
|
||||
return;
|
||||
@ -1455,6 +1477,28 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
}
|
||||
});
|
||||
|
||||
const handleToDoListSend = useLastCallback((todo: ApiNewMediaTodo) => {
|
||||
if (!currentMessageList) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isInScheduledList) {
|
||||
requestCalendar((scheduledAt) => {
|
||||
handleActionWithPaymentConfirmation(
|
||||
handleMessageSchedule,
|
||||
{ todo },
|
||||
scheduledAt,
|
||||
currentMessageList,
|
||||
);
|
||||
});
|
||||
} else {
|
||||
handleActionWithPaymentConfirmation(
|
||||
sendMessage,
|
||||
{ messageList: currentMessageList, todo, isSilent: isSilentPosting },
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
const sendSilent = useLastCallback((additionalArgs?: ScheduledMessageArgs) => {
|
||||
if (isInScheduledList) {
|
||||
requestCalendar((scheduledAt) => {
|
||||
@ -1878,6 +1922,11 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
onClear={closePollModal}
|
||||
onSend={handlePollSend}
|
||||
/>
|
||||
<ToDoListModal
|
||||
modal={todoListModal}
|
||||
onClear={closeTodoListModal}
|
||||
onSend={handleToDoListSend}
|
||||
/>
|
||||
<SendAsMenu
|
||||
isOpen={isSendAsMenuOpen}
|
||||
onClose={closeSendAsMenu}
|
||||
@ -2147,12 +2196,14 @@ const Composer: FC<OwnProps & StateProps> = ({
|
||||
isButtonVisible={!activeVoiceRecording}
|
||||
canAttachMedia={canAttachMedia}
|
||||
canAttachPolls={canAttachPolls}
|
||||
canAttachToDoLists={canAttachToDoLists}
|
||||
canSendPhotos={canSendPhotos}
|
||||
canSendVideos={canSendVideos}
|
||||
canSendDocuments={canSendDocuments}
|
||||
canSendAudios={canSendAudios}
|
||||
onFileSelect={handleFileSelect}
|
||||
onPollCreate={openPollModal}
|
||||
onTodoListCreate={handleTodoListCreate}
|
||||
isScheduled={isInScheduledList}
|
||||
attachBots={isInMessageList ? attachBots : undefined}
|
||||
peerType={attachMenuPeerType}
|
||||
@ -2458,6 +2509,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
isForwarding,
|
||||
forwardedMessagesCount: isForwarding ? forwardMessageIds!.length : undefined,
|
||||
pollModal: tabState.pollModal,
|
||||
todoListModal: tabState.todoListModal,
|
||||
stickersForEmoji: global.stickers.forEmoji.stickers,
|
||||
customEmojiForEmoji: global.customEmojis.forEmoji.stickers,
|
||||
chatFullInfo,
|
||||
|
||||
@ -12,6 +12,7 @@ import type {
|
||||
ApiPremiumSubscriptionOption,
|
||||
} from '../../../api/types';
|
||||
import type { GlobalState } from '../../../global/types';
|
||||
import type { LangPair } from '../../../types/language';
|
||||
|
||||
import { PREMIUM_BOTTOM_VIDEOS, PREMIUM_FEATURE_SECTIONS, PREMIUM_LIMITS_ORDER } from '../../../config';
|
||||
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
|
||||
@ -55,6 +56,7 @@ export const PREMIUM_FEATURE_TITLES: Record<ApiPremiumSection, string> = {
|
||||
last_seen: 'PremiumPreviewLastSeen',
|
||||
message_privacy: 'PremiumPreviewMessagePrivacy',
|
||||
effects: 'Premium.MessageEffects',
|
||||
todo: 'PremiumPreviewTodo',
|
||||
};
|
||||
|
||||
export const PREMIUM_FEATURE_DESCRIPTIONS: Record<ApiPremiumSection, string> = {
|
||||
@ -76,6 +78,7 @@ export const PREMIUM_FEATURE_DESCRIPTIONS: Record<ApiPremiumSection, string> = {
|
||||
last_seen: 'PremiumPreviewLastSeenDescription',
|
||||
message_privacy: 'PremiumPreviewMessagePrivacyDescription',
|
||||
effects: 'Premium.MessageEffectsInfo',
|
||||
todo: 'PremiumPreviewTodoDescription',
|
||||
};
|
||||
|
||||
const LIMITS_TITLES: Record<ApiLimitTypeForPromo, string> = {
|
||||
@ -290,6 +293,7 @@ const PremiumFeatureModal: FC<OwnProps> = ({
|
||||
|
||||
const i = promo.videoSections.indexOf(section);
|
||||
if (i === -1) return undefined;
|
||||
const shouldUseNewLang = promo.videoSections[i] === 'todo';
|
||||
return (
|
||||
<div className={styles.slide}>
|
||||
<div className={styles.frame}>
|
||||
@ -303,10 +307,23 @@ const PremiumFeatureModal: FC<OwnProps> = ({
|
||||
/>
|
||||
</div>
|
||||
<h1 className={styles.title}>
|
||||
{oldLang(PREMIUM_FEATURE_TITLES[promo.videoSections[i]])}
|
||||
{shouldUseNewLang
|
||||
? lang(
|
||||
PREMIUM_FEATURE_TITLES[promo.videoSections[i]] as keyof LangPair,
|
||||
undefined,
|
||||
{ withNodes: true, renderTextFilters: ['br'] },
|
||||
)
|
||||
: oldLang(PREMIUM_FEATURE_TITLES[promo.videoSections[i]])}
|
||||
</h1>
|
||||
<div className={styles.description}>
|
||||
{renderText(oldLang(PREMIUM_FEATURE_DESCRIPTIONS[promo.videoSections[i]]), ['br'])}
|
||||
{renderText(shouldUseNewLang
|
||||
? lang(
|
||||
PREMIUM_FEATURE_DESCRIPTIONS[promo.videoSections[i]] as keyof LangPair,
|
||||
undefined,
|
||||
{ withNodes: true, renderTextFilters: ['br'] },
|
||||
)
|
||||
: oldLang(PREMIUM_FEATURE_DESCRIPTIONS[promo.videoSections[i]]), ['br'],
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -9,6 +9,7 @@ import type {
|
||||
ApiPremiumPromo, ApiPremiumSection, ApiPremiumSubscriptionOption, ApiSticker, ApiStickerSet, ApiUser,
|
||||
} from '../../../api/types';
|
||||
import type { GlobalState } from '../../../global/types';
|
||||
import type { LangPair } from '../../../types/language';
|
||||
|
||||
import { PREMIUM_FEATURE_SECTIONS, TME_LINK_PREFIX } from '../../../config';
|
||||
import { getUserFullName } from '../../../global/helpers';
|
||||
@ -83,6 +84,7 @@ const PREMIUM_FEATURE_COLOR_ICONS: Record<ApiPremiumSection, string> = {
|
||||
last_seen: PremiumLastSeen,
|
||||
message_privacy: PremiumMessagePrivacy,
|
||||
effects: PremiumEffects,
|
||||
todo: PremiumBadge,
|
||||
};
|
||||
|
||||
export type OwnProps = {
|
||||
@ -148,8 +150,10 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
|
||||
if (!isOpen) {
|
||||
setHeaderHidden(true);
|
||||
setCurrentSection(undefined);
|
||||
} else if (initialSection) {
|
||||
setCurrentSection(initialSection);
|
||||
}
|
||||
}, [isOpen]);
|
||||
}, [isOpen, initialSection]);
|
||||
|
||||
const handleOpenSection = useLastCallback((section: ApiPremiumSection) => {
|
||||
setCurrentSection(section);
|
||||
@ -401,14 +405,19 @@ const PremiumMainModal: FC<OwnProps & StateProps> = ({
|
||||
</div>
|
||||
<div className={buildClassName(styles.list, isPremium && styles.noButton)}>
|
||||
{filteredSections.map((section, index) => {
|
||||
const shouldUseNewLang = section === 'todo';
|
||||
return (
|
||||
<PremiumFeatureItem
|
||||
key={section}
|
||||
title={oldLang(PREMIUM_FEATURE_TITLES[section])}
|
||||
title={shouldUseNewLang
|
||||
? lang(PREMIUM_FEATURE_TITLES[section] as keyof LangPair)
|
||||
: oldLang(PREMIUM_FEATURE_TITLES[section])}
|
||||
text={section === 'double_limits'
|
||||
? oldLang(PREMIUM_FEATURE_DESCRIPTIONS[section],
|
||||
[limitChannels, limitFolders, limitPins, limitLinks, LIMIT_ACCOUNTS])
|
||||
: oldLang(PREMIUM_FEATURE_DESCRIPTIONS[section])}
|
||||
: shouldUseNewLang
|
||||
? lang(PREMIUM_FEATURE_DESCRIPTIONS[section] as keyof LangPair)
|
||||
: oldLang(PREMIUM_FEATURE_DESCRIPTIONS[section])}
|
||||
icon={PREMIUM_FEATURE_COLOR_ICONS[section]}
|
||||
index={index}
|
||||
count={filteredSections.length}
|
||||
|
||||
@ -47,6 +47,7 @@ export type OwnProps = {
|
||||
isButtonVisible: boolean;
|
||||
canAttachMedia: boolean;
|
||||
canAttachPolls: boolean;
|
||||
canAttachToDoLists: boolean;
|
||||
canSendPhotos: boolean;
|
||||
canSendVideos: boolean;
|
||||
canSendDocuments: boolean;
|
||||
@ -58,6 +59,7 @@ export type OwnProps = {
|
||||
theme: ThemeKey;
|
||||
onFileSelect: (files: File[]) => void;
|
||||
onPollCreate: NoneToVoidFunction;
|
||||
onTodoListCreate: NoneToVoidFunction;
|
||||
onMenuOpen: NoneToVoidFunction;
|
||||
onMenuClose: NoneToVoidFunction;
|
||||
canEditMedia?: boolean;
|
||||
@ -72,6 +74,7 @@ const AttachMenu: FC<OwnProps> = ({
|
||||
isButtonVisible,
|
||||
canAttachMedia,
|
||||
canAttachPolls,
|
||||
canAttachToDoLists,
|
||||
canSendPhotos,
|
||||
canSendVideos,
|
||||
canSendDocuments,
|
||||
@ -85,6 +88,7 @@ const AttachMenu: FC<OwnProps> = ({
|
||||
onMenuOpen,
|
||||
onMenuClose,
|
||||
onPollCreate,
|
||||
onTodoListCreate,
|
||||
canEditMedia,
|
||||
editingMessage,
|
||||
messageListType,
|
||||
@ -263,6 +267,9 @@ const AttachMenu: FC<OwnProps> = ({
|
||||
{canAttachPolls && !editingMessage && (
|
||||
<MenuItem icon="poll" onClick={onPollCreate}>{oldLang('Poll')}</MenuItem>
|
||||
)}
|
||||
{canAttachToDoLists && !editingMessage && (
|
||||
<MenuItem icon="select" onClick={onTodoListCreate}>{lang('TitleToDoList')}</MenuItem>
|
||||
)}
|
||||
|
||||
{!editingMessage && !canEditMedia && !isScheduled && bots?.map((bot) => (
|
||||
<AttachBotItem
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
|
||||
.options-header {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
|
||||
font-size: 1rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-secondary);
|
||||
@ -69,13 +71,13 @@
|
||||
}
|
||||
|
||||
.note {
|
||||
font-size: smaller;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.poll-error {
|
||||
margin: -1rem 0 1rem 0.25rem;
|
||||
font-size: smaller;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-error);
|
||||
}
|
||||
|
||||
|
||||
16
src/components/middle/composer/ToDoListModal.async.tsx
Normal file
16
src/components/middle/composer/ToDoListModal.async.tsx
Normal file
@ -0,0 +1,16 @@
|
||||
import type { FC } from '../../../lib/teact/teact';
|
||||
|
||||
import type { OwnProps } from './ToDoListModal';
|
||||
|
||||
import { Bundles } from '../../../util/moduleLoader';
|
||||
|
||||
import useModuleLoader from '../../../hooks/useModuleLoader';
|
||||
|
||||
const ToDoListModalAsync: FC<OwnProps> = (props) => {
|
||||
const { modal } = props;
|
||||
const ToDoListModal = useModuleLoader(Bundles.Extra, 'ToDoListModal', !modal);
|
||||
|
||||
return ToDoListModal ? <ToDoListModal {...props} /> : undefined;
|
||||
};
|
||||
|
||||
export default ToDoListModalAsync;
|
||||
105
src/components/middle/composer/ToDoListModal.scss
Normal file
105
src/components/middle/composer/ToDoListModal.scss
Normal file
@ -0,0 +1,105 @@
|
||||
@use '../../../styles/mixins';
|
||||
|
||||
.ToDoListModal {
|
||||
.modal-dialog {
|
||||
max-width: 26.25rem;
|
||||
max-height: calc(100vh - 5rem);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
min-height: 4.875rem;
|
||||
}
|
||||
|
||||
.modal-header-condensed {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.readonly-title {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.items-header {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
|
||||
font-size: 1rem;
|
||||
font-weight: var(--font-weight-medium);
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.items-count-hint {
|
||||
margin-bottom: 0.5rem;
|
||||
margin-left: 1rem;
|
||||
font-size: 0.875rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.items-list {
|
||||
overflow: auto;
|
||||
overflow-y: scroll;
|
||||
|
||||
max-height: 20rem;
|
||||
margin: 1rem -0.75rem -0.5rem;
|
||||
padding: 0 0.75rem;
|
||||
border-top: 1px solid var(--color-chat-hover);
|
||||
|
||||
@include mixins.adapt-padding-to-scrollbar(0.75rem);
|
||||
|
||||
@media (max-width: 600px) {
|
||||
overflow: hidden;
|
||||
max-height: none;
|
||||
}
|
||||
}
|
||||
|
||||
.item-wrapper {
|
||||
position: relative;
|
||||
|
||||
.form-control {
|
||||
padding-right: 3rem;
|
||||
}
|
||||
|
||||
.item-remove-button {
|
||||
position: absolute;
|
||||
top: 0.125rem;
|
||||
right: 0.3125rem;
|
||||
}
|
||||
}
|
||||
|
||||
.options-footer {
|
||||
margin-top: 1.5rem;
|
||||
|
||||
.dialog-checkbox-group {
|
||||
margin: 0 -1.125rem;
|
||||
}
|
||||
|
||||
.options-header {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.input-group:last-child {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.8125rem;
|
||||
margin-left: -1.125rem;
|
||||
|
||||
.Radio, &:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.Checkbox,
|
||||
.Radio {
|
||||
.Checkbox-main,
|
||||
.Radio-main {
|
||||
width: 100%;
|
||||
max-height: 3rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
383
src/components/middle/composer/ToDoListModal.tsx
Normal file
383
src/components/middle/composer/ToDoListModal.tsx
Normal file
@ -0,0 +1,383 @@
|
||||
import type { ChangeEvent } from 'react';
|
||||
import type { ElementRef } from '../../../lib/teact/teact';
|
||||
import {
|
||||
memo, useEffect, useRef, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type { ApiNewMediaTodo } from '../../../api/types';
|
||||
import type { ApiMessage } from '../../../api/types';
|
||||
import type { TabState } from '../../../global/types/tabState';
|
||||
|
||||
import {
|
||||
TODO_ITEM_LENGTH_LIMIT,
|
||||
TODO_ITEMS_LIMIT,
|
||||
TODO_TITLE_LENGTH_LIMIT,
|
||||
} from '../../../config';
|
||||
import { requestMeasure, requestNextMutation } from '../../../lib/fasterdom/fasterdom';
|
||||
import { selectChatMessage } from '../../../global/selectors';
|
||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||
import { generateUniqueNumberId } from '../../../util/generateUniqueId';
|
||||
|
||||
import useCurrentOrPrev from '../../../hooks/useCurrentOrPrev';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import Button from '../../ui/Button';
|
||||
import Checkbox from '../../ui/Checkbox';
|
||||
import InputText from '../../ui/InputText';
|
||||
import Modal from '../../ui/Modal';
|
||||
|
||||
import './ToDoListModal.scss';
|
||||
|
||||
export type OwnProps = {
|
||||
modal: TabState['todoListModal'];
|
||||
onSend: (todoList: ApiNewMediaTodo) => void;
|
||||
onClear: () => void;
|
||||
};
|
||||
|
||||
export type StateProps = {
|
||||
editingMessage?: ApiMessage;
|
||||
maxItemsCount?: number;
|
||||
maxTitleLength?: number;
|
||||
maxItemLength?: number;
|
||||
};
|
||||
|
||||
type Item = {
|
||||
id: number;
|
||||
text: string;
|
||||
};
|
||||
|
||||
const MAX_LIST_HEIGHT = 320;
|
||||
const MAX_OPTION_LENGTH = 100;
|
||||
|
||||
const ToDoListModal = ({
|
||||
modal,
|
||||
maxItemsCount = TODO_ITEMS_LIMIT,
|
||||
maxTitleLength = TODO_TITLE_LENGTH_LIMIT,
|
||||
maxItemLength = TODO_ITEM_LENGTH_LIMIT,
|
||||
editingMessage,
|
||||
onSend,
|
||||
onClear,
|
||||
}: OwnProps & StateProps) => {
|
||||
const { editTodo, closeTodoListModal, appendTodoList } = getActions();
|
||||
|
||||
const titleInputRef = useRef<HTMLInputElement>();
|
||||
const itemsListRef = useRef<HTMLDivElement>();
|
||||
|
||||
const [title, setTitle] = useState<string>('');
|
||||
const [items, setItems] = useState<Item[]>([{ id: generateUniqueNumberId(), text: '' }]);
|
||||
const [isOthersCanAppend, setIsOthersCanAppend] = useState(true);
|
||||
const [isOthersCanComplete, setIsOthersCanComplete] = useState(true);
|
||||
const [hasErrors, setHasErrors] = useState<boolean>(false);
|
||||
|
||||
const isOpen = Boolean(modal);
|
||||
const renderingModal = useCurrentOrPrev(modal);
|
||||
const isAddTaskMode = renderingModal?.isAddTaskMode;
|
||||
|
||||
const lang = useLang();
|
||||
const editingTodo = editingMessage?.content.todo?.todo;
|
||||
|
||||
const focusInput = useLastCallback((ref: ElementRef<HTMLInputElement>) => {
|
||||
if (isOpen && ref.current) {
|
||||
ref.current.focus();
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (editingTodo) {
|
||||
setTitle(editingTodo.title.text);
|
||||
setIsOthersCanAppend(editingTodo.othersCanAppend ?? false);
|
||||
setIsOthersCanComplete(editingTodo.othersCanComplete ?? false);
|
||||
if (!isAddTaskMode) {
|
||||
const editingItems = editingTodo.items.map((item) => ({
|
||||
id: item.id,
|
||||
text: item.title.text,
|
||||
}));
|
||||
if (editingItems.length < maxItemsCount) {
|
||||
editingItems.push({ id: generateUniqueNumberId(), text: '' });
|
||||
}
|
||||
setItems(editingItems);
|
||||
}
|
||||
}
|
||||
}, [editingTodo, isAddTaskMode, maxItemsCount]);
|
||||
|
||||
useEffect(() => (isOpen ? captureEscKeyListener(onClear) : undefined), [isOpen, onClear]);
|
||||
useEffect(() => {
|
||||
if (!isOpen) {
|
||||
setTitle('');
|
||||
setItems([{ id: generateUniqueNumberId(), text: '' }]);
|
||||
setIsOthersCanAppend(true);
|
||||
setIsOthersCanComplete(true);
|
||||
setHasErrors(false);
|
||||
}
|
||||
}, [isOpen]);
|
||||
|
||||
useEffect(() => focusInput(titleInputRef), [focusInput, isOpen]);
|
||||
|
||||
const addNewItem = useLastCallback((newItems: Item[]) => {
|
||||
const id = generateUniqueNumberId();
|
||||
setItems([...newItems, { id, text: '' }]);
|
||||
|
||||
requestNextMutation(() => {
|
||||
const list = itemsListRef.current;
|
||||
if (!list) {
|
||||
return;
|
||||
}
|
||||
|
||||
requestMeasure(() => {
|
||||
list.scrollTo({ top: list.scrollHeight, behavior: 'smooth' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
const handleCreate = useLastCallback(() => {
|
||||
setHasErrors(false);
|
||||
if (!isOpen) {
|
||||
return;
|
||||
}
|
||||
|
||||
const todoItems = items
|
||||
.map((item) => {
|
||||
const text = item.text.trim();
|
||||
|
||||
if (!text) return undefined;
|
||||
|
||||
return {
|
||||
id: item.id,
|
||||
title: {
|
||||
text: text.substring(0, maxItemLength),
|
||||
},
|
||||
};
|
||||
}).filter(Boolean);
|
||||
|
||||
const titleTrimmed = title.trim().substring(0, maxTitleLength);
|
||||
if (!titleTrimmed || todoItems.length === 0) {
|
||||
setTitle(titleTrimmed);
|
||||
if (todoItems.length) {
|
||||
const itemsTrimmed = items.map((o) => (
|
||||
{ ...o, text: o.text.trim().substring(0, maxItemLength) }))
|
||||
.filter((o) => o.text.length);
|
||||
if (itemsTrimmed.length === 0) {
|
||||
addNewItem([]);
|
||||
} else {
|
||||
setItems([...itemsTrimmed, { id: generateUniqueNumberId(), text: '' }]);
|
||||
}
|
||||
} else {
|
||||
addNewItem([]);
|
||||
}
|
||||
setHasErrors(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAddTaskMode && editingMessage) {
|
||||
appendTodoList({
|
||||
chatId: editingMessage.chatId,
|
||||
messageId: editingMessage.id,
|
||||
items: todoItems,
|
||||
});
|
||||
closeTodoListModal();
|
||||
return;
|
||||
}
|
||||
|
||||
const payload: ApiNewMediaTodo = {
|
||||
todo: {
|
||||
title: {
|
||||
text: titleTrimmed,
|
||||
},
|
||||
items: todoItems,
|
||||
othersCanAppend: isOthersCanAppend,
|
||||
othersCanComplete: isOthersCanComplete,
|
||||
},
|
||||
};
|
||||
|
||||
if (editingMessage) {
|
||||
editTodo({
|
||||
chatId: editingMessage.chatId,
|
||||
todo: payload,
|
||||
messageId: editingMessage.id,
|
||||
});
|
||||
} else {
|
||||
onSend(payload);
|
||||
}
|
||||
|
||||
closeTodoListModal();
|
||||
});
|
||||
|
||||
const updateItem = useLastCallback((index: number, text: string) => {
|
||||
const newItems = [...items];
|
||||
newItems[index] = { ...newItems[index], text };
|
||||
if (newItems[newItems.length - 1].text.trim().length && newItems.length < maxItemsCount) {
|
||||
addNewItem(newItems);
|
||||
} else {
|
||||
setItems(newItems);
|
||||
}
|
||||
});
|
||||
|
||||
const removeItem = useLastCallback((index: number) => {
|
||||
const newItems = [...items];
|
||||
newItems.splice(index, 1);
|
||||
setItems(newItems);
|
||||
|
||||
requestNextMutation(() => {
|
||||
if (!itemsListRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
itemsListRef.current.classList.toggle('overflown', itemsListRef.current.scrollHeight > MAX_LIST_HEIGHT);
|
||||
});
|
||||
});
|
||||
|
||||
const handleIsOthersCanAppendChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setIsOthersCanAppend(e.target.checked);
|
||||
});
|
||||
const handleIsOthersCanCompleteChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setIsOthersCanComplete(e.target.checked);
|
||||
});
|
||||
|
||||
const handleKeyPress = useLastCallback((e: React.KeyboardEvent) => {
|
||||
if (e.key === 'Enter') {
|
||||
handleCreate();
|
||||
}
|
||||
});
|
||||
|
||||
const handleTitleChange = useLastCallback((e: ChangeEvent<HTMLInputElement>) => {
|
||||
setTitle(e.target.value);
|
||||
});
|
||||
|
||||
const getTitleError = useLastCallback(() => {
|
||||
if (hasErrors && !title.trim().length) {
|
||||
return lang('ToDoListErrorChooseTitle');
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const getItemsError = useLastCallback((index: number) => {
|
||||
const itemsTrimmed = items.map((o) => o.text.trim()).filter((o) => o.length);
|
||||
if (hasErrors && itemsTrimmed.length < 1 && !items[index].text.trim().length) {
|
||||
return lang('ToDoListErrorChooseTasks');
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
|
||||
function renderHeader() {
|
||||
const title = isAddTaskMode ? 'TitleAppendToDoList' : editingMessage ? 'TitleEditToDoList' : 'TitleNewToDoList';
|
||||
return (
|
||||
<div className="modal-header-condensed">
|
||||
<Button round color="translucent" size="smaller" ariaLabel={lang('AriaToDoCancel')} onClick={onClear}>
|
||||
<Icon name="close" />
|
||||
</Button>
|
||||
<div className="modal-title">{lang(title)}</div>
|
||||
<Button
|
||||
color="primary"
|
||||
size="smaller"
|
||||
className="modal-action-button"
|
||||
onClick={handleCreate}
|
||||
>
|
||||
{lang(isAddTaskMode ? 'Add' : editingMessage ? 'Save' : 'Create')}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderItems() {
|
||||
return items.map((item, index) => (
|
||||
<div className="item-wrapper">
|
||||
<InputText
|
||||
maxLength={MAX_OPTION_LENGTH}
|
||||
label={index !== items.length - 1 || items.length === maxItemsCount
|
||||
? lang('TitleTask')
|
||||
: lang('TitleAddTask')}
|
||||
error={getItemsError(index)}
|
||||
value={item.text}
|
||||
|
||||
onChange={(e) => updateItem(index, e.currentTarget.value)}
|
||||
onKeyPress={handleKeyPress}
|
||||
/>
|
||||
{index !== items.length - 1 && (
|
||||
<Button
|
||||
className="item-remove-button"
|
||||
round
|
||||
color="translucent"
|
||||
size="smaller"
|
||||
ariaLabel={lang('Delete')}
|
||||
|
||||
onClick={() => removeItem(index)}
|
||||
>
|
||||
<Icon name="close" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
));
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onClose={onClear} header={renderHeader()} className="ToDoListModal">
|
||||
{!isAddTaskMode && (
|
||||
<InputText
|
||||
ref={titleInputRef}
|
||||
label={lang('InputTitle')}
|
||||
value={title}
|
||||
error={getTitleError()}
|
||||
onChange={handleTitleChange}
|
||||
onKeyPress={handleKeyPress}
|
||||
/>
|
||||
)}
|
||||
{isAddTaskMode && (
|
||||
<div className="readonly-title">
|
||||
{title}
|
||||
</div>
|
||||
)}
|
||||
<div className="options-divider" />
|
||||
|
||||
<div className="options-list custom-scroll" ref={itemsListRef}>
|
||||
<h3 className="items-header">
|
||||
{lang(isAddTaskMode ? 'ToDoListNewTasks' : 'TitleToDoList')}
|
||||
</h3>
|
||||
|
||||
{renderItems()}
|
||||
|
||||
</div>
|
||||
|
||||
<div className="items-count-hint">
|
||||
{lang('HintTodoListTasksCount', {
|
||||
count: maxItemsCount - items.length - (isAddTaskMode && editingTodo ? editingTodo.items.length : 0),
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="options-divider" />
|
||||
|
||||
{!isAddTaskMode && (
|
||||
<div className="options-footer">
|
||||
<div className="dialog-checkbox-group">
|
||||
<Checkbox
|
||||
label={lang('AllowOthersAddTasks')}
|
||||
checked={isOthersCanAppend}
|
||||
onChange={handleIsOthersCanAppendChange}
|
||||
/>
|
||||
<Checkbox
|
||||
label={lang('AllowOthersMarkAsDone')}
|
||||
checked={isOthersCanComplete}
|
||||
onChange={handleIsOthersCanCompleteChange}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { modal }): StateProps => {
|
||||
const { appConfig } = global;
|
||||
const editingMessage = modal?.messageId ? selectChatMessage(global, modal.chatId, modal.messageId) : undefined;
|
||||
return {
|
||||
editingMessage,
|
||||
maxItemsCount: appConfig?.todoItemsMax,
|
||||
maxTitleLength: appConfig?.todoTitleLengthMax,
|
||||
maxItemLength: appConfig?.todoItemLengthMax,
|
||||
};
|
||||
},
|
||||
)(ToDoListModal));
|
||||
@ -64,7 +64,12 @@
|
||||
|
||||
.messageLink {
|
||||
overflow: hidden;
|
||||
min-width: 0;
|
||||
min-width: 1ch;
|
||||
}
|
||||
|
||||
.noEllipsis {
|
||||
overflow: visible;
|
||||
min-width: auto;
|
||||
}
|
||||
|
||||
.singleLine, .messageLink {
|
||||
|
||||
@ -94,6 +94,8 @@ const SINGLE_LINE_ACTIONS = new Set<ApiMessageAction['type']>([
|
||||
'pinMessage',
|
||||
'chatEditPhoto',
|
||||
'chatDeletePhoto',
|
||||
'todoCompletions',
|
||||
'todoAppendTasks',
|
||||
'unsupported',
|
||||
]);
|
||||
const HIDDEN_TEXT_ACTIONS = new Set<ApiMessageAction['type']>(['giftCode', 'prizeStars', 'suggestProfilePhoto']);
|
||||
|
||||
@ -12,6 +12,7 @@ import {
|
||||
getMainUsername,
|
||||
getMessageInvoice, getMessageText, isChatChannel,
|
||||
} from '../../../global/helpers';
|
||||
import { getMessageContent } from '../../../global/helpers';
|
||||
import { getPeerTitle } from '../../../global/helpers/peers';
|
||||
import { getMessageReplyInfo } from '../../../global/helpers/replies';
|
||||
import {
|
||||
@ -534,14 +535,14 @@ const ActionMessageText = ({
|
||||
if (isRecurringInit) {
|
||||
return lang(
|
||||
'ActionPaymentInitRecurringFor',
|
||||
{ amount: cost, user: chatLink, invoice: renderMessageLink(replyMessage!, invoiceTitle, asPreview) },
|
||||
{ amount: cost, user: chatLink, invoice: renderMessageLink(replyMessage, invoiceTitle, asPreview) },
|
||||
{ withNodes: true },
|
||||
);
|
||||
}
|
||||
|
||||
return lang(
|
||||
'ActionPaymentDoneFor',
|
||||
{ amount: cost, user: chatLink, invoice: renderMessageLink(replyMessage!, invoiceTitle, asPreview) },
|
||||
{ amount: cost, user: chatLink, invoice: renderMessageLink(replyMessage, invoiceTitle, asPreview) },
|
||||
{ withNodes: true },
|
||||
);
|
||||
}
|
||||
@ -749,6 +750,146 @@ const ActionMessageText = ({
|
||||
}, { withNodes: true, withMarkdown: true });
|
||||
}
|
||||
|
||||
case 'todoCompletions': {
|
||||
const { completedIds, incompletedIds } = action;
|
||||
|
||||
let completedItem;
|
||||
let incompletedItem;
|
||||
const { todo } = replyMessage ? getMessageContent(replyMessage) : {};
|
||||
if (todo) {
|
||||
const todoItems = todo.todo.items;
|
||||
completedItem = todoItems.find((item) => completedIds.includes(item.id));
|
||||
incompletedItem = todoItems.find((item) => incompletedIds.includes(item.id));
|
||||
}
|
||||
|
||||
if (incompletedItem) {
|
||||
const incompletedTaskTitle = incompletedItem.title;
|
||||
|
||||
const incompletedTaskLink = renderMessageLink(
|
||||
replyMessage,
|
||||
renderTextWithEntities({
|
||||
text: incompletedTaskTitle.text,
|
||||
entities: incompletedTaskTitle.entities,
|
||||
asPreview: true,
|
||||
}),
|
||||
asPreview,
|
||||
);
|
||||
|
||||
return translateWithYou(lang, 'MessageActionTodoCompletionsAsNotDone', isOutgoing, {
|
||||
peer: senderLink,
|
||||
task: incompletedTaskLink,
|
||||
});
|
||||
}
|
||||
|
||||
if (completedItem) {
|
||||
const completedTaskTitle = completedItem.title;
|
||||
const completedTaskLink = renderMessageLink(
|
||||
replyMessage,
|
||||
renderTextWithEntities({
|
||||
text: completedTaskTitle.text,
|
||||
entities: completedTaskTitle.entities,
|
||||
asPreview: true,
|
||||
}),
|
||||
asPreview,
|
||||
);
|
||||
|
||||
return translateWithYou(lang, 'MessageActionTodoCompletionsAsDone', isOutgoing, {
|
||||
peer: senderLink,
|
||||
task: completedTaskLink,
|
||||
});
|
||||
}
|
||||
|
||||
if (completedIds) {
|
||||
const completedText = lang('MessageActionTodoTaskCount', {
|
||||
count: completedIds.length,
|
||||
}, { pluralValue: completedIds.length });
|
||||
const completedLink = renderMessageLink(
|
||||
replyMessage,
|
||||
renderTextWithEntities({
|
||||
text: completedText,
|
||||
asPreview: true,
|
||||
}),
|
||||
asPreview,
|
||||
{ noEllipsis: true },
|
||||
);
|
||||
return translateWithYou(lang, 'MessageActionTodoCompletionsAsDone', isOutgoing, {
|
||||
peer: senderLink,
|
||||
task: completedLink,
|
||||
});
|
||||
}
|
||||
|
||||
const incompletedText = lang('MessageActionTodoTaskCount', {
|
||||
count: incompletedIds.length,
|
||||
}, { pluralValue: incompletedIds.length });
|
||||
const incompletedLink = renderMessageLink(
|
||||
replyMessage,
|
||||
renderTextWithEntities({
|
||||
text: incompletedText,
|
||||
asPreview: true,
|
||||
}),
|
||||
asPreview,
|
||||
{ noEllipsis: true },
|
||||
);
|
||||
|
||||
return translateWithYou(lang, 'MessageActionTodoCompletionsAsNotDone', isOutgoing, {
|
||||
peer: senderLink,
|
||||
task: incompletedLink,
|
||||
});
|
||||
}
|
||||
|
||||
case 'todoAppendTasks': {
|
||||
const { items } = action;
|
||||
const { todo } = replyMessage ? getMessageContent(replyMessage) : {};
|
||||
|
||||
const listTitle = todo?.todo.title.text || '';
|
||||
const listLink = renderMessageLink(
|
||||
replyMessage,
|
||||
renderTextWithEntities({
|
||||
text: listTitle,
|
||||
asPreview: true,
|
||||
}),
|
||||
asPreview,
|
||||
);
|
||||
|
||||
if (items.length === 1) {
|
||||
const taskTitle = items[0].title;
|
||||
const taskLink = renderMessageLink(
|
||||
replyMessage,
|
||||
renderTextWithEntities({
|
||||
text: taskTitle.text,
|
||||
entities: taskTitle.entities,
|
||||
asPreview: true,
|
||||
}),
|
||||
asPreview,
|
||||
);
|
||||
|
||||
return translateWithYou(lang, 'MessageActionAppendTodo', isOutgoing, {
|
||||
peer: senderLink,
|
||||
task: taskLink,
|
||||
list: listLink,
|
||||
});
|
||||
}
|
||||
|
||||
const tasksText = lang('MessageActionTodoTaskCount', {
|
||||
count: items.length,
|
||||
}, { pluralValue: items.length });
|
||||
const tasksLink = renderMessageLink(
|
||||
replyMessage,
|
||||
renderTextWithEntities({
|
||||
text: tasksText,
|
||||
asPreview: true,
|
||||
}),
|
||||
asPreview,
|
||||
{ noEllipsis: true },
|
||||
);
|
||||
|
||||
return translateWithYou(lang, 'MessageActionAppendTodoMultiple', isOutgoing, {
|
||||
peer: senderLink,
|
||||
tasks: tasksLink,
|
||||
list: listLink,
|
||||
});
|
||||
}
|
||||
|
||||
case 'phoneCall': // Rendered as a regular message, but considered an action for the summary
|
||||
return lang(getCallMessageKey(action, isOutgoing));
|
||||
default:
|
||||
|
||||
@ -25,6 +25,9 @@ import type {
|
||||
} from '../../../types';
|
||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||
|
||||
import {
|
||||
TODO_ITEMS_LIMIT,
|
||||
} from '../../../config';
|
||||
import { PREVIEW_AVATAR_COUNT, SERVICE_NOTIFICATIONS_USER_ID } from '../../../config';
|
||||
import {
|
||||
areReactionsEmpty,
|
||||
@ -79,6 +82,7 @@ import { getSelectionAsFormattedText } from './helpers/getSelectionAsFormattedTe
|
||||
import { isSelectionRangeInsideMessage } from './helpers/isSelectionRangeInsideMessage';
|
||||
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
import useSchedule from '../../../hooks/useSchedule';
|
||||
@ -125,6 +129,7 @@ type StateProps = {
|
||||
canDelete?: boolean;
|
||||
canReport?: boolean;
|
||||
canEdit?: boolean;
|
||||
canAppendTodoList?: boolean;
|
||||
canForward?: boolean;
|
||||
canFaveSticker?: boolean;
|
||||
canUnfaveSticker?: boolean;
|
||||
@ -192,6 +197,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
canReport,
|
||||
canShowReactionList,
|
||||
canEdit,
|
||||
canAppendTodoList,
|
||||
enabledReactions,
|
||||
reactionsLimit,
|
||||
isPrivate,
|
||||
@ -265,9 +271,12 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
addLocalPaidReaction,
|
||||
openPaidReactionModal,
|
||||
reportMessages,
|
||||
openTodoListModal,
|
||||
showNotification,
|
||||
} = getActions();
|
||||
|
||||
const lang = useOldLang();
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
const { ref: containerRef } = useShowTransition({
|
||||
isOpen,
|
||||
onCloseAnimationEnd,
|
||||
@ -435,7 +444,34 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
|
||||
const handleEdit = useLastCallback(() => {
|
||||
setEditingId({ messageId: message.id });
|
||||
if (message.content.todo) {
|
||||
openTodoListModal({
|
||||
chatId: message.chatId,
|
||||
messageId: message.id,
|
||||
});
|
||||
} else {
|
||||
setEditingId({ messageId: message.id });
|
||||
}
|
||||
closeMenu();
|
||||
});
|
||||
|
||||
const handleAppendTodoList = useLastCallback(() => {
|
||||
if (!isCurrentUserPremium) {
|
||||
showNotification({
|
||||
message: lang('SubscribeToTelegramPremiumForAppendToDo'),
|
||||
action: {
|
||||
action: 'openPremiumModal',
|
||||
payload: { initialSection: 'todo' },
|
||||
},
|
||||
actionText: oldLang('PremiumMore'),
|
||||
});
|
||||
} else {
|
||||
openTodoListModal({
|
||||
chatId: message.chatId,
|
||||
messageId: message.id,
|
||||
isAddTaskMode: true,
|
||||
});
|
||||
}
|
||||
closeMenu();
|
||||
});
|
||||
|
||||
@ -667,6 +703,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
repliesThreadInfo={repliesThreadInfo}
|
||||
canUnpin={canUnpin}
|
||||
canEdit={canEdit}
|
||||
canAppendTodoList={canAppendTodoList}
|
||||
canForward={canForward}
|
||||
canFaveSticker={canFaveSticker}
|
||||
canUnfaveSticker={canUnfaveSticker}
|
||||
@ -695,6 +732,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
onOpenThread={handleOpenThread}
|
||||
onReply={handleReply}
|
||||
onEdit={handleEdit}
|
||||
onAppendTodoList={handleAppendTodoList}
|
||||
onPin={handlePin}
|
||||
onUnpin={handleUnpin}
|
||||
onForward={handleForward}
|
||||
@ -734,8 +772,8 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
<ConfirmDialog
|
||||
isOpen={isClosePollDialogOpen}
|
||||
onClose={closeClosePollDialog}
|
||||
text={lang('lng_polls_stop_warning')}
|
||||
confirmLabel={lang('lng_polls_stop_sure')}
|
||||
text={oldLang('lng_polls_stop_warning')}
|
||||
confirmLabel={oldLang('lng_polls_stop_sure')}
|
||||
confirmHandler={handlePollClose}
|
||||
/>
|
||||
{canReschedule && calendar}
|
||||
@ -855,6 +893,9 @@ export default memo(withGlobal<OwnProps>(
|
||||
const canGift = selectCanGift(global, message.chatId);
|
||||
|
||||
const savedDialogId = selectSavedDialogIdFromMessage(global, message);
|
||||
const todoItemsMax = global.appConfig?.todoItemsMax || TODO_ITEMS_LIMIT;
|
||||
const canAppendTodoList = message.content.todo?.todo.othersCanAppend
|
||||
&& message.content.todo?.todo.items?.length < todoItemsMax;
|
||||
|
||||
return {
|
||||
threadId,
|
||||
@ -871,6 +912,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
canUnpin: !isScheduled && canUnpin,
|
||||
canDelete,
|
||||
canEdit: !isPinned && canEdit,
|
||||
canAppendTodoList,
|
||||
canForward: !isScheduled && canForward,
|
||||
canFaveSticker: !isScheduled && canFaveSticker,
|
||||
canUnfaveSticker: !isScheduled && canUnfaveSticker,
|
||||
|
||||
@ -189,6 +189,7 @@ import RoundVideo from './RoundVideo';
|
||||
import Sticker from './Sticker';
|
||||
import Story from './Story';
|
||||
import StoryMention from './StoryMention';
|
||||
import TodoList from './TodoList';
|
||||
import Video from './Video';
|
||||
import WebPage from './WebPage';
|
||||
|
||||
@ -520,7 +521,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
voice, document, sticker, contact,
|
||||
webPage, invoice, location,
|
||||
action, game, storyData, giveaway,
|
||||
giveawayResults,
|
||||
giveawayResults, todo,
|
||||
} = getMessageContent(message);
|
||||
|
||||
const messageReplyInfo = getMessageReplyInfo(message);
|
||||
@ -1241,6 +1242,9 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
{poll && (
|
||||
<Poll message={message} poll={poll} onSendVote={handleVoteSend} />
|
||||
)}
|
||||
{todo && (
|
||||
<TodoList message={message} todoList={todo} />
|
||||
)}
|
||||
{(giveaway || giveawayResults) && (
|
||||
<Giveaway message={message} />
|
||||
)}
|
||||
|
||||
@ -32,6 +32,7 @@ import { getMessageCopyOptions } from './helpers/copyOptions';
|
||||
|
||||
import useAppLayout from '../../../hooks/useAppLayout';
|
||||
import useFlag from '../../../hooks/useFlag';
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
import useOldLang from '../../../hooks/useOldLang';
|
||||
|
||||
@ -73,6 +74,7 @@ type OwnProps = {
|
||||
canShowReactionList?: boolean;
|
||||
canBuyPremium?: boolean;
|
||||
canEdit?: boolean;
|
||||
canAppendTodoList?: boolean;
|
||||
canForward?: boolean;
|
||||
canFaveSticker?: boolean;
|
||||
canUnfaveSticker?: boolean;
|
||||
@ -101,6 +103,7 @@ type OwnProps = {
|
||||
onReply?: NoneToVoidFunction;
|
||||
onOpenThread?: VoidFunction;
|
||||
onEdit?: NoneToVoidFunction;
|
||||
onAppendTodoList?: NoneToVoidFunction;
|
||||
onPin?: NoneToVoidFunction;
|
||||
onUnpin?: NoneToVoidFunction;
|
||||
onForward?: NoneToVoidFunction;
|
||||
@ -159,6 +162,7 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
canReply,
|
||||
canQuote,
|
||||
canEdit,
|
||||
canAppendTodoList,
|
||||
noReplies,
|
||||
canPin,
|
||||
canUnpin,
|
||||
@ -192,6 +196,7 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
onReply,
|
||||
onOpenThread,
|
||||
onEdit,
|
||||
onAppendTodoList,
|
||||
onPin,
|
||||
onUnpin,
|
||||
onForward,
|
||||
@ -228,7 +233,8 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
} = getActions();
|
||||
const menuRef = useRef<HTMLDivElement>();
|
||||
const scrollableRef = useRef<HTMLDivElement>();
|
||||
const lang = useOldLang();
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
const noReactions = !isPrivate && !enabledReactions;
|
||||
const areReactionsPossible = message.areReactionsPossible;
|
||||
const withReactions = (canShowReactionList && !noReactions) || areReactionsPossible;
|
||||
@ -248,7 +254,7 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
|
||||
const handleAfterCopy = useLastCallback(() => {
|
||||
showNotification({
|
||||
message: lang('Share.Link.Copied'),
|
||||
message: oldLang('Share.Link.Copied'),
|
||||
});
|
||||
onClose();
|
||||
});
|
||||
@ -395,40 +401,49 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
'MessageContextMenu_items scrollable-content custom-scroll',
|
||||
areItemsHidden && 'MessageContextMenu_items-hidden',
|
||||
)}
|
||||
dir={lang.isRtl ? 'rtl' : undefined}
|
||||
dir={oldLang.isRtl ? 'rtl' : undefined}
|
||||
>
|
||||
{shouldShowGiftButton
|
||||
&& (
|
||||
<MenuItem icon="gift" onClick={handleGiftClick}>
|
||||
{message?.isOutgoing ? lang('SendAnotherGift')
|
||||
: lang('Conversation.ContextMenuSendGiftTo', userFullName)}
|
||||
{message?.isOutgoing ? oldLang('SendAnotherGift')
|
||||
: oldLang('Conversation.ContextMenuSendGiftTo', userFullName)}
|
||||
</MenuItem>
|
||||
)}
|
||||
{canSendNow && <MenuItem icon="send-outline" onClick={onSend}>{lang('MessageScheduleSend')}</MenuItem>}
|
||||
{canSendNow && <MenuItem icon="send-outline" onClick={onSend}>{oldLang('MessageScheduleSend')}</MenuItem>}
|
||||
{canReschedule && (
|
||||
<MenuItem icon="schedule" onClick={onReschedule}>{lang('MessageScheduleEditTime')}</MenuItem>
|
||||
<MenuItem icon="schedule" onClick={onReschedule}>{oldLang('MessageScheduleEditTime')}</MenuItem>
|
||||
)}
|
||||
{canReply && (
|
||||
<MenuItem icon="reply" onClick={onReply}>
|
||||
{lang(canQuote ? 'lng_context_quote_and_reply' : 'Reply')}
|
||||
{oldLang(canQuote ? 'lng_context_quote_and_reply' : 'Reply')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{!noReplies && Boolean(repliesThreadInfo?.messagesCount) && (
|
||||
<MenuItem icon="replies" onClick={onOpenThread}>
|
||||
{lang('Conversation.ContextViewReplies', repliesThreadInfo.messagesCount, 'i')}
|
||||
{oldLang('Conversation.ContextViewReplies', repliesThreadInfo.messagesCount, 'i')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{canEdit && <MenuItem icon="edit" onClick={onEdit}>{oldLang('Edit')}</MenuItem>}
|
||||
{canAppendTodoList && (
|
||||
<MenuItem icon="add" onClick={onAppendTodoList}>
|
||||
{lang('MenuButtonAppendTodoList')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{canEdit && <MenuItem icon="edit" onClick={onEdit}>{lang('Edit')}</MenuItem>}
|
||||
{canFaveSticker && (
|
||||
<MenuItem icon="favorite" onClick={onFaveSticker}>{lang('AddToFavorites')}</MenuItem>
|
||||
<MenuItem icon="favorite" onClick={onFaveSticker}>{oldLang('AddToFavorites')}</MenuItem>
|
||||
)}
|
||||
{canUnfaveSticker && (
|
||||
<MenuItem icon="favorite" onClick={onUnfaveSticker}>{lang('Stickers.RemoveFromFavorites')}</MenuItem>
|
||||
<MenuItem icon="favorite" onClick={onUnfaveSticker}>{oldLang('Stickers.RemoveFromFavorites')}</MenuItem>
|
||||
)}
|
||||
{canTranslate && <MenuItem icon="language" onClick={onTranslate}>{oldLang('TranslateMessage')}</MenuItem>}
|
||||
{canShowOriginal && (
|
||||
<MenuItem icon="language" onClick={onShowOriginal}>
|
||||
{oldLang('ShowOriginalButton')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{canTranslate && <MenuItem icon="language" onClick={onTranslate}>{lang('TranslateMessage')}</MenuItem>}
|
||||
{canShowOriginal && <MenuItem icon="language" onClick={onShowOriginal}>{lang('ShowOriginalButton')}</MenuItem>}
|
||||
{canSelectLanguage && (
|
||||
<MenuItem icon="web" onClick={onSelectLanguage}>{lang('lng_settings_change_lang')}</MenuItem>
|
||||
<MenuItem icon="web" onClick={onSelectLanguage}>{oldLang('lng_settings_change_lang')}</MenuItem>
|
||||
)}
|
||||
{copyOptions.map((option) => (
|
||||
<MenuItem
|
||||
@ -437,23 +452,23 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
onClick={option.handler}
|
||||
withPreventDefaultOnMouseDown
|
||||
>
|
||||
{lang(option.label)}
|
||||
{oldLang(option.label)}
|
||||
</MenuItem>
|
||||
))}
|
||||
{canPin && <MenuItem icon="pin" onClick={onPin}>{lang('DialogPin')}</MenuItem>}
|
||||
{canUnpin && <MenuItem icon="unpin" onClick={onUnpin}>{lang('DialogUnpin')}</MenuItem>}
|
||||
{canSaveGif && <MenuItem icon="gifs" onClick={onSaveGif}>{lang('lng_context_save_gif')}</MenuItem>}
|
||||
{canRevote && <MenuItem icon="revote" onClick={onCancelVote}>{lang('lng_polls_retract')}</MenuItem>}
|
||||
{canClosePoll && <MenuItem icon="stop" onClick={onClosePoll}>{lang('lng_polls_stop')}</MenuItem>}
|
||||
{canPin && <MenuItem icon="pin" onClick={onPin}>{oldLang('DialogPin')}</MenuItem>}
|
||||
{canUnpin && <MenuItem icon="unpin" onClick={onUnpin}>{oldLang('DialogUnpin')}</MenuItem>}
|
||||
{canSaveGif && <MenuItem icon="gifs" onClick={onSaveGif}>{oldLang('lng_context_save_gif')}</MenuItem>}
|
||||
{canRevote && <MenuItem icon="revote" onClick={onCancelVote}>{oldLang('lng_polls_retract')}</MenuItem>}
|
||||
{canClosePoll && <MenuItem icon="stop" onClick={onClosePoll}>{oldLang('lng_polls_stop')}</MenuItem>}
|
||||
{canDownload && (
|
||||
<MenuItem icon="download" onClick={onDownload}>
|
||||
{isDownloading ? lang('lng_context_cancel_download') : lang('lng_media_download')}
|
||||
{isDownloading ? oldLang('lng_context_cancel_download') : oldLang('lng_media_download')}
|
||||
</MenuItem>
|
||||
)}
|
||||
{canForward && <MenuItem icon="forward" onClick={onForward}>{lang('Forward')}</MenuItem>}
|
||||
{canSelect && <MenuItem icon="select" onClick={onSelect}>{lang('Common.Select')}</MenuItem>}
|
||||
{canReport && <MenuItem icon="flag" onClick={onReport}>{lang('lng_context_report_msg')}</MenuItem>}
|
||||
{canDelete && <MenuItem destructive icon="delete" onClick={onDelete}>{lang('Delete')}</MenuItem>}
|
||||
{canForward && <MenuItem icon="forward" onClick={onForward}>{oldLang('Forward')}</MenuItem>}
|
||||
{canSelect && <MenuItem icon="select" onClick={onSelect}>{oldLang('Common.Select')}</MenuItem>}
|
||||
{canReport && <MenuItem icon="flag" onClick={onReport}>{oldLang('lng_context_report_msg')}</MenuItem>}
|
||||
{canDelete && <MenuItem destructive icon="delete" onClick={onDelete}>{oldLang('Delete')}</MenuItem>}
|
||||
{hasCustomEmoji && (
|
||||
<>
|
||||
<MenuSeparator size="thick" />
|
||||
@ -465,12 +480,14 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
)}
|
||||
{customEmojiSets && customEmojiSets.length === 1 && (
|
||||
<MenuItem withWrap onClick={handleOpenCustomEmojiSets} className="menu-custom-emoji-sets">
|
||||
{renderText(lang('MessageContainsEmojiPack', customEmojiSets[0].title), ['simple_markdown', 'emoji'])}
|
||||
{renderText(
|
||||
oldLang('MessageContainsEmojiPack', customEmojiSets[0].title), ['simple_markdown', 'emoji'],
|
||||
)}
|
||||
</MenuItem>
|
||||
)}
|
||||
{customEmojiSets && customEmojiSets.length > 1 && (
|
||||
<MenuItem withWrap onClick={handleOpenCustomEmojiSets} className="menu-custom-emoji-sets">
|
||||
{renderText(lang('MessageContainsEmojiPacks', customEmojiSets.length), ['simple_markdown'])}
|
||||
{renderText(oldLang('MessageContainsEmojiPacks', customEmojiSets.length), ['simple_markdown'])}
|
||||
</MenuItem>
|
||||
)}
|
||||
</>
|
||||
@ -484,14 +501,14 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
disabled={!canShowReactionsCount && !seenByDatesCount}
|
||||
>
|
||||
<span className="MessageContextMenu--seen-by-label-wrapper">
|
||||
<span className="MessageContextMenu--seen-by-label" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||
<span className="MessageContextMenu--seen-by-label" dir={oldLang.isRtl ? 'rtl' : undefined}>
|
||||
{canShowReactionsCount && message.reactors?.count ? (
|
||||
canShowSeenBy && seenByDatesCount
|
||||
? lang(
|
||||
? oldLang(
|
||||
'Chat.OutgoingContextMixedReactionCount',
|
||||
[message.reactors.count, seenByDatesCount],
|
||||
)
|
||||
: lang('Chat.ContextReactionCount', message.reactors.count, 'i')
|
||||
: oldLang('Chat.ContextReactionCount', message.reactors.count, 'i')
|
||||
) : (
|
||||
seenByDatesCount === 1 && seenByRecentPeers
|
||||
? renderText(
|
||||
@ -500,8 +517,8 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
: (seenByRecentPeers[0] as ApiChat).title,
|
||||
) : (
|
||||
seenByDatesCount
|
||||
? lang('Conversation.ContextMenuSeen', seenByDatesCount, 'i')
|
||||
: lang('Conversation.ContextMenuNoViews')
|
||||
? oldLang('Conversation.ContextMenuSeen', seenByDatesCount, 'i')
|
||||
: oldLang('Conversation.ContextMenuNoViews')
|
||||
)
|
||||
)}
|
||||
</span>
|
||||
|
||||
141
src/components/middle/message/TodoList.scss
Normal file
141
src/components/middle/message/TodoList.scss
Normal file
@ -0,0 +1,141 @@
|
||||
.todo-list {
|
||||
min-width: 15rem;
|
||||
text-align: initial;
|
||||
|
||||
.todo-list-readonly-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.todo-readonly-item-checkbox {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin-inline-end: 0.5rem;
|
||||
|
||||
font-weight: var(--font-weight-semibold);
|
||||
color: var(--accent-color);
|
||||
}
|
||||
|
||||
.readonly-item-label {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.todo-item-bullet-point {
|
||||
width: 0.25rem;
|
||||
height: 0.25rem;
|
||||
border-radius: 50%;
|
||||
background-color: var(--accent-color);
|
||||
}
|
||||
|
||||
.completed-label {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.Checkbox {
|
||||
min-height: 2.5rem;
|
||||
padding-bottom: 1.25rem;
|
||||
padding-left: 2.5rem;
|
||||
|
||||
&.withSubLabel {
|
||||
padding-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 1 !important;
|
||||
|
||||
.Checkbox-main {
|
||||
&::before,
|
||||
&::after {
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.Checkbox-main {
|
||||
padding: 0 !important;
|
||||
|
||||
&::before {
|
||||
--color-borders-input: var(--secondary-color);
|
||||
|
||||
background-color: var(--background-color);
|
||||
}
|
||||
|
||||
&::after {
|
||||
background-color: var(--accent-color);
|
||||
outline: 1px solid var(--background-color);
|
||||
|
||||
.theme-dark & {
|
||||
background-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTYiIGhlaWdodD0iMTIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTEzLjkuOEw1LjggOC45IDIuMSA1LjJjLS40LS40LTEuMS0uNC0xLjYgMC0uNC40LS40IDEuMSAwIDEuNkw1IDExLjJjLjQuNCAxLjEuNCAxLjYgMGw4LjktOC45Yy40LS40LjQtMS4xIDAtMS42LS41LS40LTEuMi0uNC0xLjYuMXoiIGZpbGw9IiM3NjZhYzgiLz48L3N2Zz4=);
|
||||
}
|
||||
}
|
||||
|
||||
.user-avatar,
|
||||
&::before,
|
||||
&::after {
|
||||
top: 0.6875rem;
|
||||
left: 0.125rem;
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
left: 0.875rem;
|
||||
}
|
||||
|
||||
.label {
|
||||
line-height: 1.3125rem;
|
||||
}
|
||||
}
|
||||
|
||||
input:checked ~ .Checkbox-main {
|
||||
&::before {
|
||||
border-color: var(--accent-color);
|
||||
}
|
||||
}
|
||||
|
||||
.Spinner {
|
||||
top: 0.6875rem;
|
||||
left: 0.125rem;
|
||||
}
|
||||
|
||||
&.loading {
|
||||
.Spinner {
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.todo-list-title {
|
||||
margin: 0.125rem 0;
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 1.25rem;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.list-type,
|
||||
.completed-tasks-count {
|
||||
font-size: 0.875rem;
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
.list-type {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.completed-tasks-count {
|
||||
margin: 0 0 1.125rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
min-width: 50vw;
|
||||
}
|
||||
}
|
||||
197
src/components/middle/message/TodoList.tsx
Normal file
197
src/components/middle/message/TodoList.tsx
Normal file
@ -0,0 +1,197 @@
|
||||
import {
|
||||
memo, useEffect, useMemo, useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
|
||||
import type {
|
||||
ApiMediaTodo,
|
||||
ApiMessage,
|
||||
ApiPeer,
|
||||
} from '../../../api/types';
|
||||
|
||||
import { getPeerFullTitle, getPeerTitle } from '../../../global/helpers/peers';
|
||||
import { selectIsCurrentUserPremium, selectSender, selectUser } from '../../../global/selectors';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
|
||||
|
||||
import useLang from '../../../hooks/useLang';
|
||||
import useLastCallback from '../../../hooks/useLastCallback';
|
||||
|
||||
import AnimatedCounter from '../../common/AnimatedCounter';
|
||||
import Icon from '../../common/icons/Icon';
|
||||
import CheckboxGroup from '../../ui/CheckboxGroup';
|
||||
|
||||
import './TodoList.scss';
|
||||
|
||||
type OwnProps = {
|
||||
message: ApiMessage;
|
||||
todoList: ApiMediaTodo;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
sender?: ApiPeer;
|
||||
isCurrentUserPremium: boolean;
|
||||
isSynced?: boolean;
|
||||
};
|
||||
|
||||
const TodoList = ({
|
||||
message,
|
||||
todoList,
|
||||
sender,
|
||||
isCurrentUserPremium,
|
||||
isSynced,
|
||||
}: OwnProps & StateProps) => {
|
||||
const { toggleTodoCompleted, showNotification } = getActions();
|
||||
const { todo, completions } = todoList;
|
||||
const { title, items, othersCanComplete } = todo;
|
||||
const [completedTasks, setCompletedTasks] = useState<string[]>([]);
|
||||
const completedTasksSet = useMemo(() => new Set(completedTasks), [completedTasks]);
|
||||
|
||||
const canToggle = !message.isScheduled && isCurrentUserPremium && isSynced;
|
||||
|
||||
useEffect(() => {
|
||||
const completedIds = completions?.map((c) => c.itemId.toString()) || [];
|
||||
setCompletedTasks(completedIds);
|
||||
}, [completions]);
|
||||
|
||||
const lang = useLang();
|
||||
|
||||
const handleTaskLabelClick = useLastCallback((e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (!isCurrentUserPremium) {
|
||||
showNotification({
|
||||
message: lang('SubscribeToTelegramPremiumForToggleTask'),
|
||||
action: {
|
||||
action: 'openPremiumModal',
|
||||
payload: { initialSection: 'todo' },
|
||||
},
|
||||
actionText: lang('PremiumMore'),
|
||||
});
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
const handleTaskToggle = useLastCallback((newCompletedTasks: string[]) => {
|
||||
const newCompletedId = newCompletedTasks.find((id) => !completedTasksSet.has(id));
|
||||
const newIncompletedId = Array.from(completedTasksSet).find((id) => !newCompletedTasks.includes(id));
|
||||
|
||||
toggleTodoCompleted({
|
||||
chatId: message.chatId,
|
||||
messageId: message.id,
|
||||
completedIds: newCompletedId ? [Number(newCompletedId)] : [],
|
||||
incompletedIds: newIncompletedId ? [Number(newIncompletedId)] : [],
|
||||
});
|
||||
});
|
||||
const isReadOnly = Boolean(message.forwardInfo) || (!othersCanComplete && !message.isOutgoing);
|
||||
const isOutgoing = message.isOutgoing;
|
||||
|
||||
const tasks = useMemo(() => items.map((task) => {
|
||||
const user = !othersCanComplete ? undefined : selectUser(getGlobal(),
|
||||
completions?.find((c) => c.itemId === task.id)?.completedBy || '');
|
||||
const subLabel = user ? getPeerFullTitle(lang, user) : undefined;
|
||||
return {
|
||||
label: renderTextWithEntities(task.title),
|
||||
value: task.id.toString(),
|
||||
user,
|
||||
subLabel,
|
||||
};
|
||||
}), [items, othersCanComplete, completions, lang]);
|
||||
|
||||
const renderCheckBoxGroup = () => {
|
||||
return (
|
||||
<CheckboxGroup
|
||||
options={tasks}
|
||||
selected={completedTasks}
|
||||
onChange={handleTaskToggle}
|
||||
onClickLabel={!isCurrentUserPremium ? handleTaskLabelClick : undefined}
|
||||
disabled={!canToggle}
|
||||
isRound
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const renderReadOnlyTodoList = () => {
|
||||
return (
|
||||
<div className="todo-list-items">
|
||||
{tasks.map((task) => (
|
||||
<div
|
||||
key={task.value}
|
||||
className="todo-list-readonly-item"
|
||||
>
|
||||
<div className="todo-readonly-item-checkbox">
|
||||
{completedTasksSet.has(task.value)
|
||||
? <Icon name="check" />
|
||||
: <div className="todo-item-bullet-point" />}
|
||||
</div>
|
||||
<div
|
||||
className={buildClassName(
|
||||
'readonly-item-label',
|
||||
completedTasksSet.has(task.value) && 'completed-label',
|
||||
)}
|
||||
>
|
||||
{task.label}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const renderTodoListType = () => {
|
||||
if (message.forwardInfo) {
|
||||
return lang('TitleToDoList');
|
||||
}
|
||||
|
||||
if (othersCanComplete) {
|
||||
return lang('TitleGroupToDoList');
|
||||
}
|
||||
|
||||
if (isOutgoing) {
|
||||
return lang('TitleYourToDoList');
|
||||
}
|
||||
|
||||
if (sender) {
|
||||
return lang('TitleUserToDoList', { peer: getPeerTitle(lang, sender) }, { withNodes: true });
|
||||
}
|
||||
|
||||
return lang('TitleToDoList');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="todo-list" dir={lang.isRtl ? 'auto' : 'ltr'}>
|
||||
<div className="todo-list-header">
|
||||
<div className="todo-list-title">
|
||||
{renderTextWithEntities(title)}
|
||||
</div>
|
||||
<div className="list-type">
|
||||
{renderTodoListType()}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="todo-list-items">
|
||||
{isReadOnly ? renderReadOnlyTodoList() : renderCheckBoxGroup()}
|
||||
</div>
|
||||
<div className="completed-tasks-count">
|
||||
<AnimatedCounter text={
|
||||
lang('DescriptionCompletedToDoTasks', {
|
||||
number: completedTasks.length,
|
||||
count: tasks.length,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>((global, { message }): StateProps => {
|
||||
const sender = selectSender(global, message);
|
||||
return {
|
||||
sender,
|
||||
isCurrentUserPremium: selectIsCurrentUserPremium(global),
|
||||
isSynced: global.isSynced,
|
||||
};
|
||||
},
|
||||
)(TodoList));
|
||||
@ -103,12 +103,21 @@ export function renderPeerLink(peerId: string | undefined, text: string, asPrevi
|
||||
);
|
||||
}
|
||||
|
||||
export function renderMessageLink(targetMessage: ApiMessage, text: TeactNode, asPreview?: boolean) {
|
||||
if (asPreview) return text;
|
||||
export function renderMessageLink(
|
||||
targetMessage: ApiMessage | undefined,
|
||||
text: TeactNode,
|
||||
asPreview: boolean | undefined,
|
||||
params?: {
|
||||
noEllipsis?: boolean;
|
||||
},
|
||||
) {
|
||||
const { noEllipsis } = params || {};
|
||||
|
||||
if (asPreview || !targetMessage) return text;
|
||||
|
||||
return (
|
||||
<Link
|
||||
className={styles.messageLink}
|
||||
|
||||
className={buildClassName(styles.messageLink, noEllipsis && styles.noEllipsis)}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
getActions().focusMessage({ chatId: targetMessage.chatId, messageId: targetMessage.id });
|
||||
|
||||
@ -24,7 +24,7 @@ type StateProps = {
|
||||
};
|
||||
|
||||
const ChatRefundModal = ({ modal, user }: OwnProps & StateProps) => {
|
||||
const { closeChatRefundModal, addNoPaidMessagesException } = getActions();
|
||||
const { closeChatRefundModal, toggleNoPaidMessagesException } = getActions();
|
||||
|
||||
const [shouldRefundStars, setShouldRefundStars] = useState(false);
|
||||
|
||||
@ -40,7 +40,7 @@ const ChatRefundModal = ({ modal, user }: OwnProps & StateProps) => {
|
||||
const handleConfirmRemoveFee = useLastCallback(() => {
|
||||
closeChatRefundModal();
|
||||
if (!userId) return;
|
||||
addNoPaidMessagesException({ userId, shouldRefundCharged: shouldRefundStars });
|
||||
toggleNoPaidMessagesException ({ userId, shouldRefundCharged: shouldRefundStars });
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@ -27,6 +27,16 @@
|
||||
}
|
||||
}
|
||||
|
||||
.user-avatar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
transition: opacity 0.15s ease;
|
||||
}
|
||||
|
||||
&.round {
|
||||
.Checkbox-main {
|
||||
&::before, &::after {
|
||||
@ -101,11 +111,6 @@
|
||||
pointer-events: none;
|
||||
content: "";
|
||||
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 1.125rem; // 1 + ((1.5 - 1.25) / 2)
|
||||
transform: translateY(-50%);
|
||||
|
||||
display: block;
|
||||
|
||||
width: 1.25rem;
|
||||
@ -127,6 +132,15 @@
|
||||
transition: opacity 0.1s ease;
|
||||
}
|
||||
|
||||
.user-avatar,
|
||||
&::before,
|
||||
&::after {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 1.125rem; // 1 + ((1.5 - 1.25) / 2)
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
|
||||
.label {
|
||||
unicode-bidi: plaintext;
|
||||
line-height: 1.25rem;
|
||||
@ -202,6 +216,11 @@
|
||||
&::after {
|
||||
opacity: 1;
|
||||
}
|
||||
.user-avatar {
|
||||
&.user-avatar-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&[dir="rtl"] {
|
||||
@ -218,6 +237,7 @@
|
||||
}
|
||||
|
||||
.Checkbox-main {
|
||||
.user-avatar,
|
||||
&::before,
|
||||
&::after {
|
||||
right: 1rem;
|
||||
|
||||
@ -7,15 +7,19 @@ import {
|
||||
useState,
|
||||
} from '../../lib/teact/teact';
|
||||
|
||||
import type { ApiUser } from '../../api/types';
|
||||
import type { IconName } from '../../types/icons';
|
||||
import type { IRadioOption } from './CheckboxGroup';
|
||||
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import { REM } from '../common/helpers/mediaDimensions';
|
||||
import renderText from '../common/helpers/renderText';
|
||||
|
||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
|
||||
import Avatar from '../common/Avatar';
|
||||
import Icon from '../common/icons/Icon';
|
||||
import Button from './Button';
|
||||
import Spinner from './Spinner';
|
||||
@ -26,6 +30,7 @@ type OwnProps = {
|
||||
id?: string;
|
||||
name?: string;
|
||||
value?: string;
|
||||
user?: ApiUser;
|
||||
label?: TeactNode;
|
||||
labelText?: TeactNode;
|
||||
subLabel?: string;
|
||||
@ -50,12 +55,14 @@ type OwnProps = {
|
||||
leftElement?: TeactNode;
|
||||
values?: string[];
|
||||
};
|
||||
const AVATAR_SIZE = 1.25 * REM;
|
||||
|
||||
const Checkbox: FC<OwnProps> = ({
|
||||
id,
|
||||
name,
|
||||
value,
|
||||
label,
|
||||
user,
|
||||
labelText,
|
||||
subLabel,
|
||||
checked,
|
||||
@ -81,6 +88,7 @@ const Checkbox: FC<OwnProps> = ({
|
||||
const lang = useOldLang();
|
||||
const labelRef = useRef<HTMLLabelElement>();
|
||||
const [showNested, setShowNested] = useState(false);
|
||||
const renderingUser = useCurrentOrPrev(user, true);
|
||||
|
||||
const handleChange = useLastCallback((event: ChangeEvent<HTMLInputElement>) => {
|
||||
if (disabled) {
|
||||
@ -151,6 +159,14 @@ const Checkbox: FC<OwnProps> = ({
|
||||
Boolean(leftElement) && 'Nested-avatar-list',
|
||||
)}
|
||||
>
|
||||
<div className={buildClassName('user-avatar', renderingUser && 'user-avatar-visible')}>
|
||||
{renderingUser && (
|
||||
<Avatar
|
||||
peer={renderingUser}
|
||||
size={AVATAR_SIZE}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<span className="label" dir="auto">
|
||||
{leftElement}
|
||||
{typeof label === 'string' ? renderText(label) : label}
|
||||
|
||||
@ -28,6 +28,7 @@ type OwnProps = {
|
||||
loadingOptions?: string[];
|
||||
isRound?: boolean;
|
||||
onChange: (value: string[]) => void;
|
||||
onClickLabel?: (e: React.MouseEvent, value?: string) => void;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
@ -40,6 +41,7 @@ const CheckboxGroup: FC<OwnProps> = ({
|
||||
loadingOptions,
|
||||
isRound,
|
||||
onChange,
|
||||
onClickLabel,
|
||||
className,
|
||||
}) => {
|
||||
const handleChange = useLastCallback((event: ChangeEvent<HTMLInputElement>, nestedOptionList?: IRadioOption) => {
|
||||
@ -87,10 +89,12 @@ const CheckboxGroup: FC<OwnProps> = ({
|
||||
label={option.label}
|
||||
subLabel={option.subLabel}
|
||||
value={option.value}
|
||||
user={option.user}
|
||||
checked={selected?.indexOf(option.value) !== -1}
|
||||
disabled={option.disabled || disabled}
|
||||
isLoading={loadingOptions ? loadingOptions.indexOf(option.value) !== -1 : undefined}
|
||||
onChange={handleChange}
|
||||
onClickLabel={onClickLabel}
|
||||
nestedCheckbox={nestedCheckbox}
|
||||
nestedCheckboxCount={getCheckedNestedCount(option.nestedOptions ?? [])}
|
||||
nestedOptionList={option}
|
||||
|
||||
@ -106,6 +106,9 @@ export const STORY_LIST_LIMIT = 100;
|
||||
export const API_GENERAL_ID_LIMIT = 100;
|
||||
export const STATISTICS_PUBLIC_FORWARDS_LIMIT = 50;
|
||||
export const RESALE_GIFTS_LIMIT = 50;
|
||||
export const TODO_ITEMS_LIMIT = 30;
|
||||
export const TODO_TITLE_LENGTH_LIMIT = 32;
|
||||
export const TODO_ITEM_LENGTH_LIMIT = 64;
|
||||
|
||||
export const STORY_VIEWS_MIN_SEARCH = 15;
|
||||
export const STORY_MIN_REACTIONS_SORT = 10;
|
||||
@ -429,6 +432,7 @@ export const PREMIUM_FEATURE_SECTIONS = [
|
||||
'last_seen',
|
||||
'message_privacy',
|
||||
'effects',
|
||||
'todo',
|
||||
] as const;
|
||||
|
||||
export const PREMIUM_BOTTOM_VIDEOS: ApiPremiumSection[] = [
|
||||
@ -444,6 +448,7 @@ export const PREMIUM_BOTTOM_VIDEOS: ApiPremiumSection[] = [
|
||||
'last_seen',
|
||||
'message_privacy',
|
||||
'effects',
|
||||
'todo',
|
||||
];
|
||||
|
||||
export const PREMIUM_LIMITS_ORDER: ApiLimitTypeForPromo[] = [
|
||||
|
||||
@ -53,6 +53,7 @@ import { getTranslationFn, type RegularLangFnParameters } from '../../../util/lo
|
||||
import { formatStarsAsText } from '../../../util/localization/format';
|
||||
import { oldTranslate } from '../../../util/oldLangProvider';
|
||||
import { debounce, onTickEnd, rafPromise } from '../../../util/schedulers';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import { callApi, cancelApiProgress } from '../../../api/gramjs';
|
||||
import {
|
||||
getIsSavedDialog,
|
||||
@ -146,6 +147,7 @@ import {
|
||||
selectUserStatus,
|
||||
selectViewportIds,
|
||||
} from '../../selectors';
|
||||
import { updateWithLocalMedia } from '../apiUpdaters/messages';
|
||||
import { deleteMessages } from '../apiUpdaters/messages';
|
||||
|
||||
const AUTOLOGIN_TOKEN_KEY = 'autologin_token';
|
||||
@ -557,6 +559,24 @@ addActionHandler('editMessage', (global, actions, payload): ActionReturnType =>
|
||||
})();
|
||||
});
|
||||
|
||||
addActionHandler('editTodo', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
chatId, todo, messageId,
|
||||
} = payload;
|
||||
|
||||
const chat = selectChat(global, chatId);
|
||||
const message = selectChatMessage(global, chatId, messageId);
|
||||
if (!chat || !message) {
|
||||
return;
|
||||
}
|
||||
|
||||
callApi('editTodo', {
|
||||
chat,
|
||||
message,
|
||||
todo,
|
||||
});
|
||||
});
|
||||
|
||||
addActionHandler('cancelUploadMedia', (global, actions, payload): ActionReturnType => {
|
||||
const { chatId, messageId } = payload;
|
||||
|
||||
@ -1138,6 +1158,71 @@ addActionHandler('sendPollVote', (global, actions, payload): ActionReturnType =>
|
||||
}
|
||||
});
|
||||
|
||||
addActionHandler('toggleTodoCompleted', (global, actions, payload): ActionReturnType => {
|
||||
const { chatId, messageId, completedIds, incompletedIds } = payload;
|
||||
const chat = selectChat(global, chatId);
|
||||
const message = selectChatMessage(global, chatId, messageId);
|
||||
const currentUserId = global.currentUserId;
|
||||
|
||||
const currentTodo = message?.content.todo;
|
||||
if (!currentTodo || !currentUserId || !chat) {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentCompletions = currentTodo.completions || [];
|
||||
const currentCompletionIds = currentCompletions.map((c) => c.itemId);
|
||||
|
||||
const newCompletions = [...currentCompletions];
|
||||
const now = getServerTime();
|
||||
|
||||
completedIds.forEach((itemId) => {
|
||||
if (!currentCompletionIds.includes(itemId)) {
|
||||
newCompletions.push({
|
||||
itemId,
|
||||
completedBy: currentUserId,
|
||||
completedAt: now,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const finalCompletions = newCompletions.filter((c) => !incompletedIds.includes(c.itemId));
|
||||
|
||||
const newContent = {
|
||||
...message.content,
|
||||
todo: {
|
||||
...currentTodo,
|
||||
completions: finalCompletions,
|
||||
},
|
||||
};
|
||||
|
||||
const messageUpdate: Partial<ApiMessage> = {
|
||||
...message,
|
||||
content: newContent,
|
||||
};
|
||||
|
||||
global = updateWithLocalMedia(global, chatId, message.id, messageUpdate);
|
||||
setGlobal(global);
|
||||
|
||||
callApi('toggleTodoCompleted', { chat, messageId: message.id, completedIds, incompletedIds });
|
||||
});
|
||||
addActionHandler('appendTodoList', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
chatId, items, messageId,
|
||||
} = payload;
|
||||
|
||||
const chat = selectChat(global, chatId);
|
||||
const message = selectChatMessage(global, chatId, messageId);
|
||||
if (!chat || !message) {
|
||||
return;
|
||||
}
|
||||
|
||||
callApi('appendTodoList', {
|
||||
chat,
|
||||
message,
|
||||
items,
|
||||
});
|
||||
});
|
||||
|
||||
addActionHandler('cancelPollVote', (global, actions, payload): ActionReturnType => {
|
||||
const { chatId, messageId } = payload;
|
||||
const chat = selectChat(global, chatId);
|
||||
|
||||
@ -14,6 +14,7 @@ import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||
import * as langProvider from '../../../util/oldLangProvider';
|
||||
import { getStripeError } from '../../../util/payments/stripe';
|
||||
import { buildQueryString } from '../../../util/requestQuery';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import { extractCurrentThemeParams } from '../../../util/themeStyle';
|
||||
import { callApi } from '../../../api/gramjs';
|
||||
import { isChatChannel, isChatSuperGroup } from '../../helpers';
|
||||
@ -827,7 +828,7 @@ addActionHandler('applyBoost', async (global, actions, payload): Promise<void> =
|
||||
const oldMyBoosts = tabState.boostModal?.myBoosts;
|
||||
|
||||
if (oldMyBoosts) {
|
||||
const unixNow = Math.floor(Date.now() / 1000);
|
||||
const unixNow = getServerTime();
|
||||
const newMyBoosts = oldMyBoosts.map((boost) => {
|
||||
if (slots.includes(boost.slot)) {
|
||||
return {
|
||||
|
||||
@ -191,14 +191,14 @@ addActionHandler('loadCommonChats', async (global, actions, payload): Promise<vo
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('addNoPaidMessagesException', async (global, actions, payload): Promise<void> => {
|
||||
addActionHandler('toggleNoPaidMessagesException', async (global, actions, payload): Promise<void> => {
|
||||
const { userId, shouldRefundCharged } = payload;
|
||||
const user = selectUser(global, userId);
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await callApi('addNoPaidMessagesException',
|
||||
const result = await callApi('toggleNoPaidMessagesException',
|
||||
{ user, shouldRefundCharged });
|
||||
if (!result) {
|
||||
return;
|
||||
|
||||
@ -940,7 +940,7 @@ function updateReactions<T extends GlobalState>(
|
||||
return global;
|
||||
}
|
||||
|
||||
function updateWithLocalMedia(
|
||||
export function updateWithLocalMedia(
|
||||
global: RequiredGlobalState,
|
||||
chatId: string,
|
||||
id: number,
|
||||
|
||||
@ -32,6 +32,7 @@ import {
|
||||
isChatChannel,
|
||||
} from '../../helpers';
|
||||
import { getMessageSummaryText } from '../../helpers/messageSummary';
|
||||
import { addTabStateResetterAction } from '../../helpers/meta';
|
||||
import { getPeerTitle } from '../../helpers/peers';
|
||||
import { renderMessageSummaryHtml } from '../../helpers/renderMessageSummaryHtml';
|
||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||
@ -762,6 +763,22 @@ addActionHandler('closePollModal', (global, actions, payload): ActionReturnType
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addActionHandler('openTodoListModal', (global, actions, payload): ActionReturnType => {
|
||||
const {
|
||||
chatId, messageId, isAddTaskMode, tabId = getCurrentTabId(),
|
||||
} = payload;
|
||||
|
||||
return updateTabState(global, {
|
||||
todoListModal: {
|
||||
chatId,
|
||||
messageId,
|
||||
isAddTaskMode,
|
||||
},
|
||||
}, tabId);
|
||||
});
|
||||
|
||||
addTabStateResetterAction('closeTodoListModal', 'todoListModal');
|
||||
|
||||
addActionHandler('checkVersionNotification', (global, actions): ActionReturnType => {
|
||||
if (RELEASE_DATETIME && Date.now() > Number(RELEASE_DATETIME) + VERSION_NOTIFICATION_DURATION) {
|
||||
return;
|
||||
|
||||
@ -189,6 +189,7 @@ export interface IAllowedAttachmentOptions {
|
||||
canSendVoices: boolean;
|
||||
canSendPlainText: boolean;
|
||||
canSendDocuments: boolean;
|
||||
canAttachToDoLists: boolean;
|
||||
}
|
||||
|
||||
export function getAllowedAttachmentOptions(
|
||||
@ -214,6 +215,7 @@ export function getAllowedAttachmentOptions(
|
||||
canSendVoices: false,
|
||||
canSendPlainText: false,
|
||||
canSendDocuments: false,
|
||||
canAttachToDoLists: false,
|
||||
};
|
||||
}
|
||||
|
||||
@ -224,6 +226,7 @@ export function getAllowedAttachmentOptions(
|
||||
canAttachPolls: !isStoryReply && !chat.isMonoforum
|
||||
&& (isAdmin || !isUserRightBanned(chat, 'sendPolls', chatFullInfo))
|
||||
&& (!isUserId(chat.id) || isChatWithBot || isSavedMessages),
|
||||
canAttachToDoLists: !isStoryReply && !chat.isMonoforum && !isChatChannel(chat),
|
||||
canSendStickers: isAdmin || isStoryReply || !isUserRightBanned(chat, 'sendStickers', chatFullInfo),
|
||||
canSendGifs: isAdmin || isStoryReply || !isUserRightBanned(chat, 'sendGifs', chatFullInfo),
|
||||
canAttachEmbedLinks: !isStoryReply && (isAdmin || !isUserRightBanned(chat, 'embedLinks', chatFullInfo)),
|
||||
|
||||
@ -51,6 +51,7 @@ export function hasMessageMedia(message: MediaContainer) {
|
||||
|| getMessageSticker(message)
|
||||
|| getMessageContact(message)
|
||||
|| getMessagePollId(message)
|
||||
|| getMessageTodo(message)
|
||||
|| getMessageAction(message)
|
||||
|| getMessageAudio(message)
|
||||
|| getMessageVoice(message)
|
||||
@ -128,6 +129,10 @@ export function getMessagePollId(message: MediaContainer) {
|
||||
return message.content.pollId;
|
||||
}
|
||||
|
||||
export function getMessageTodo(message: MediaContainer) {
|
||||
return message.content.todo;
|
||||
}
|
||||
|
||||
export function getMessageInvoice(message: MediaContainer) {
|
||||
return message.content.invoice;
|
||||
}
|
||||
|
||||
@ -72,6 +72,7 @@ export function getMessageSummaryEmoji(message: ApiMessage) {
|
||||
sticker,
|
||||
pollId,
|
||||
paidMedia,
|
||||
todo,
|
||||
} = message.content;
|
||||
|
||||
if (message.groupedId || photo || paidMedia) {
|
||||
@ -102,6 +103,10 @@ export function getMessageSummaryEmoji(message: ApiMessage) {
|
||||
return '📊';
|
||||
}
|
||||
|
||||
if (todo) {
|
||||
return '📝';
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -143,6 +148,7 @@ function getSummaryDescription(
|
||||
giveaway,
|
||||
giveawayResults,
|
||||
paidMedia,
|
||||
todo,
|
||||
} = mediaContent;
|
||||
const { poll } = statefulContent || {};
|
||||
|
||||
@ -239,6 +245,10 @@ function getSummaryDescription(
|
||||
summary = truncatedText || (message ? lang('ForwardedStory') : lang('Chat.ReplyStory'));
|
||||
}
|
||||
|
||||
if (todo) {
|
||||
summary = lang('Chat.Todo.Message.Title');
|
||||
}
|
||||
|
||||
return summary || CONTENT_NOT_SUPPORTED;
|
||||
}
|
||||
|
||||
|
||||
@ -52,13 +52,14 @@ export function getMessageTranscription(message: ApiMessage) {
|
||||
|
||||
export function hasMessageText(message: MediaContainer) {
|
||||
const {
|
||||
action, text, sticker, photo, video, audio, voice, document, pollId, webPage, contact, invoice, location,
|
||||
game, storyData, giveaway, giveawayResults, paidMedia,
|
||||
action, text, sticker, photo, video, audio, voice, document, pollId, todo,
|
||||
webPage, contact, invoice, location, game, storyData, giveaway, giveawayResults, paidMedia,
|
||||
} = message.content;
|
||||
|
||||
return Boolean(text) || !(
|
||||
sticker || photo || video || audio || voice || document || contact || pollId || webPage || invoice || location
|
||||
|| game || storyData || giveaway || giveawayResults || paidMedia || action?.type === 'phoneCall'
|
||||
sticker || photo || video || audio || voice || document || contact || pollId || todo || webPage
|
||||
|| invoice || location || game || storyData || giveaway || giveawayResults
|
||||
|| paidMedia || action?.type === 'phoneCall'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@ -23,6 +23,7 @@ import type {
|
||||
ApiMessage,
|
||||
ApiMessageEntity,
|
||||
ApiMessageSearchContext,
|
||||
ApiNewMediaTodo,
|
||||
ApiNotification,
|
||||
ApiNotifyPeerType,
|
||||
ApiPaymentStatus,
|
||||
@ -45,6 +46,7 @@ import type {
|
||||
ApiStickerSet,
|
||||
ApiStickerSetInfo,
|
||||
ApiThemeParameters,
|
||||
ApiTodoItem,
|
||||
ApiTypePrepaidGiveaway,
|
||||
ApiUpdate,
|
||||
ApiUser,
|
||||
@ -502,6 +504,11 @@ export interface ActionPayloads {
|
||||
attachments?: ApiAttachment[];
|
||||
entities?: ApiMessageEntity[];
|
||||
} & WithTabId;
|
||||
editTodo: {
|
||||
chatId: string;
|
||||
todo: ApiNewMediaTodo;
|
||||
messageId: number;
|
||||
} & WithTabId;
|
||||
deleteHistory: {
|
||||
chatId: string;
|
||||
shouldDeleteForAll?: boolean;
|
||||
@ -1380,6 +1387,17 @@ export interface ActionPayloads {
|
||||
messageId: number;
|
||||
options: string[];
|
||||
};
|
||||
toggleTodoCompleted: {
|
||||
chatId: string;
|
||||
messageId: number;
|
||||
completedIds: number[];
|
||||
incompletedIds: number[];
|
||||
};
|
||||
appendTodoList: {
|
||||
chatId: string;
|
||||
items: ApiTodoItem[];
|
||||
messageId: number;
|
||||
} & WithTabId;
|
||||
cancelPollVote: {
|
||||
chatId: string;
|
||||
messageId: number;
|
||||
@ -1779,7 +1797,7 @@ export interface ActionPayloads {
|
||||
isMuted?: boolean;
|
||||
shouldSharePhoneNumber?: boolean;
|
||||
} & WithTabId;
|
||||
addNoPaidMessagesException: {
|
||||
toggleNoPaidMessagesException: {
|
||||
userId: string;
|
||||
shouldRefundCharged: boolean;
|
||||
};
|
||||
@ -2182,6 +2200,12 @@ export interface ActionPayloads {
|
||||
isQuiz?: boolean;
|
||||
} & WithTabId) | undefined;
|
||||
closePollModal: WithTabId | undefined;
|
||||
openTodoListModal: {
|
||||
chatId: string;
|
||||
messageId?: number;
|
||||
isAddTaskMode?: boolean;
|
||||
} & WithTabId;
|
||||
closeTodoListModal: WithTabId | undefined;
|
||||
requestConfetti: (ConfettiParams & WithTabId) | WithTabId;
|
||||
requestWave: {
|
||||
startX: number;
|
||||
|
||||
@ -20,6 +20,7 @@ import type {
|
||||
ApiMessage,
|
||||
ApiMissingInvitedUser,
|
||||
ApiMyBoost,
|
||||
ApiNewMediaTodo,
|
||||
ApiNewPoll,
|
||||
ApiNotification,
|
||||
ApiPaymentFormRegular,
|
||||
@ -141,6 +142,7 @@ export type TabState = {
|
||||
gif?: ApiVideo;
|
||||
sticker?: ApiSticker;
|
||||
poll?: ApiNewPoll;
|
||||
todo?: ApiNewMediaTodo;
|
||||
isSilent?: boolean;
|
||||
sendGrouped?: boolean;
|
||||
sendCompressed?: boolean;
|
||||
@ -530,6 +532,12 @@ export type TabState = {
|
||||
isQuiz?: boolean;
|
||||
};
|
||||
|
||||
todoListModal?: {
|
||||
chatId: string;
|
||||
messageId?: number;
|
||||
isAddTaskMode?: boolean;
|
||||
};
|
||||
|
||||
preparedMessageModal?: {
|
||||
message: ApiPreparedInlineMessage;
|
||||
webAppKey: string;
|
||||
|
||||
@ -12,5 +12,5 @@ for (const tl of Object.values(Api)) {
|
||||
}
|
||||
}
|
||||
|
||||
export const LAYER = 204;
|
||||
export const LAYER = 205;
|
||||
export { tlobjects };
|
||||
|
||||
169
src/lib/gramjs/tl/api.d.ts
vendored
169
src/lib/gramjs/tl/api.d.ts
vendored
File diff suppressed because one or more lines are too long
@ -37,6 +37,7 @@ inputMediaDice#e66fbf7b emoticon:string = InputMedia;
|
||||
inputMediaStory#89fdd778 peer:InputPeer id:int = InputMedia;
|
||||
inputMediaWebPage#c21b8849 flags:# force_large_media:flags.0?true force_small_media:flags.1?true optional:flags.2?true url:string = InputMedia;
|
||||
inputMediaPaidMedia#c4103386 flags:# stars_amount:long extended_media:Vector<InputMedia> payload:flags.0?string = InputMedia;
|
||||
inputMediaTodo#9fc55fde todo:TodoList = InputMedia;
|
||||
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
|
||||
inputChatUploadedPhoto#bdcdaec0 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.3?VideoSize = InputChatPhoto;
|
||||
inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto;
|
||||
@ -83,7 +84,7 @@ chatForbidden#6592a1a7 id:long title:string = Chat;
|
||||
channel#fe685355 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# stories_hidden:flags2.1?true stories_hidden_min:flags2.2?true stories_unavailable:flags2.3?true signature_profiles:flags2.12?true autotranslation:flags2.15?true broadcast_messages_allowed:flags2.16?true monoforum:flags2.17?true forum_tabs:flags2.19?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector<RestrictionReason> admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector<Username> stories_max_id:flags2.4?int color:flags2.7?PeerColor profile_color:flags2.8?PeerColor emoji_status:flags2.9?EmojiStatus level:flags2.10?int subscription_until_date:flags2.11?int bot_verification_icon:flags2.13?long send_paid_messages_stars:flags2.14?long linked_monoforum_id:flags2.18?long = Chat;
|
||||
channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
|
||||
chatFull#2633421b flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector<long> available_reactions:flags.18?ChatReactions reactions_limit:flags.20?int = ChatFull;
|
||||
channelFull#52d6806b flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true paid_messages_available:flags2.20?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int = ChatFull;
|
||||
channelFull#e07429de flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true paid_messages_available:flags2.20?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int send_paid_messages_stars:flags2.21?long = ChatFull;
|
||||
chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant;
|
||||
chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant;
|
||||
chatParticipantAdmin#a0933f5b user_id:long inviter_id:long date:int = ChatParticipant;
|
||||
@ -93,7 +94,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto;
|
||||
chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto;
|
||||
messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message;
|
||||
message#eabcdd4d flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true flags2:# offline:flags2.1?true video_processing_pending:flags2.4?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long via_business_bot_id:flags2.0?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector<RestrictionReason> ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int effect:flags2.2?long factcheck:flags2.3?FactCheck report_delivery_until_date:flags2.5?int paid_message_stars:flags2.6?long = Message;
|
||||
messageService#d3d28540 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true reactions_are_possible:flags.9?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction reactions:flags.20?MessageReactions ttl_period:flags.25?int = Message;
|
||||
messageService#7a800e0a flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true reactions_are_possible:flags.9?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer saved_peer_id:flags.28?Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction reactions:flags.20?MessageReactions ttl_period:flags.25?int = Message;
|
||||
messageMediaEmpty#3ded6320 = MessageMedia;
|
||||
messageMediaPhoto#695150d7 flags:# spoiler:flags.3?true photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia;
|
||||
messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia;
|
||||
@ -111,6 +112,7 @@ messageMediaStory#68cb6283 flags:# via_mention:flags.1?true peer:Peer id:int sto
|
||||
messageMediaGiveaway#aa073beb flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.2?true channels:Vector<long> countries_iso2:flags.1?Vector<string> prize_description:flags.3?string quantity:int months:flags.4?int stars:flags.5?long until_date:int = MessageMedia;
|
||||
messageMediaGiveawayResults#ceaa3ea1 flags:# only_new_subscribers:flags.0?true refunded:flags.2?true channel_id:long additional_peers_count:flags.3?int launch_msg_id:int winners_count:int unclaimed_count:int winners:Vector<long> months:flags.4?int stars:flags.5?long prize_description:flags.1?string until_date:int = MessageMedia;
|
||||
messageMediaPaidMedia#a8852491 stars_amount:long extended_media:Vector<MessageExtendedMedia> = MessageMedia;
|
||||
messageMediaToDo#8a53b014 flags:# todo:TodoList completions:flags.0?Vector<TodoCompletion> = MessageMedia;
|
||||
messageActionEmpty#b6aef7b0 = MessageAction;
|
||||
messageActionChatCreate#bd47cbad title:string users:Vector<long> = MessageAction;
|
||||
messageActionChatEditTitle#b5a1ce5a title:string = MessageAction;
|
||||
@ -162,6 +164,8 @@ messageActionStarGiftUnique#2e3ae60e flags:# upgrade:flags.0?true transferred:fl
|
||||
messageActionPaidMessagesRefunded#ac1f1fcd count:int stars:long = MessageAction;
|
||||
messageActionPaidMessagesPrice#84b88578 flags:# broadcast_messages_allowed:flags.0?true stars:long = MessageAction;
|
||||
messageActionConferenceCall#2ffe2f7a flags:# missed:flags.0?true active:flags.1?true video:flags.4?true call_id:long duration:flags.2?int other_participants:flags.3?Vector<Peer> = MessageAction;
|
||||
messageActionTodoCompletions#cc7c5c89 completed:Vector<int> incompleted:Vector<int> = MessageAction;
|
||||
messageActionTodoAppendTasks#c7edbc83 list:Vector<TodoItem> = MessageAction;
|
||||
dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog;
|
||||
dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
|
||||
photoEmpty#2331b22d id:long = Photo;
|
||||
@ -1087,8 +1091,8 @@ botCommandScopePeerUser#a1321f3 peer:InputPeer user_id:InputUser = BotCommandSco
|
||||
account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordResult;
|
||||
account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult;
|
||||
account.resetPasswordOk#e926d63e = account.ResetPasswordResult;
|
||||
sponsoredMessage#4d93a990 flags:# recommended:flags.5?true can_report:flags.12?true random_id:bytes url:string title:string message:string entities:flags.1?Vector<MessageEntity> photo:flags.6?Photo media:flags.14?MessageMedia color:flags.13?PeerColor button_text:string sponsor_info:flags.7?string additional_info:flags.8?string = SponsoredMessage;
|
||||
messages.sponsoredMessages#c9ee1d87 flags:# posts_between:flags.0?int messages:Vector<SponsoredMessage> chats:Vector<Chat> users:Vector<User> = messages.SponsoredMessages;
|
||||
sponsoredMessage#7dbf8673 flags:# recommended:flags.5?true can_report:flags.12?true random_id:bytes url:string title:string message:string entities:flags.1?Vector<MessageEntity> photo:flags.6?Photo media:flags.14?MessageMedia color:flags.13?PeerColor button_text:string sponsor_info:flags.7?string additional_info:flags.8?string min_display_duration:flags.15?int max_display_duration:flags.15?int = SponsoredMessage;
|
||||
messages.sponsoredMessages#ffda656d flags:# posts_between:flags.0?int start_delay:flags.1?int between_delay:flags.2?int messages:Vector<SponsoredMessage> chats:Vector<Chat> users:Vector<User> = messages.SponsoredMessages;
|
||||
messages.sponsoredMessagesEmpty#1839490f = messages.SponsoredMessages;
|
||||
searchResultsCalendarPeriod#c9b0539f date:int min_msg_id:int max_msg_id:int count:int = SearchResultsCalendarPeriod;
|
||||
messages.searchResultsCalendar#147ee23c flags:# inexact:flags.0?true count:int min_date:int min_msg_id:int offset_id_offset:flags.1?int periods:Vector<SearchResultsCalendarPeriod> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.SearchResultsCalendar;
|
||||
@ -1283,7 +1287,7 @@ storyReactionPublicForward#bbab2643 message:Message = StoryReaction;
|
||||
storyReactionPublicRepost#cfcd0f13 peer_id:Peer story:StoryItem = StoryReaction;
|
||||
stories.storyReactionsList#aa5f789c flags:# count:int reactions:Vector<StoryReaction> chats:Vector<Chat> users:Vector<User> next_offset:flags.0?string = stories.StoryReactionsList;
|
||||
savedDialog#bd87cb6c flags:# pinned:flags.2?true peer:Peer top_message:int = SavedDialog;
|
||||
monoForumDialog#64407ea7 flags:# unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_reactions_count:int draft:flags.1?DraftMessage = SavedDialog;
|
||||
monoForumDialog#64407ea7 flags:# unread_mark:flags.3?true nopaid_messages_exception:flags.4?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_reactions_count:int draft:flags.1?DraftMessage = SavedDialog;
|
||||
messages.savedDialogs#f83ae221 dialogs:Vector<SavedDialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.SavedDialogs;
|
||||
messages.savedDialogsSlice#44ba9dd9 count:int dialogs:Vector<SavedDialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.SavedDialogs;
|
||||
messages.savedDialogsNotModified#c01f6fe8 count:int = messages.SavedDialogs;
|
||||
@ -1438,6 +1442,9 @@ starGiftAttributeCounter#2eb1b658 attribute:StarGiftAttributeId count:int = Star
|
||||
payments.resaleStarGifts#947a12df flags:# count:int gifts:Vector<StarGift> next_offset:flags.0?string attributes:flags.1?Vector<StarGiftAttribute> attributes_hash:flags.1?long chats:Vector<Chat> counters:flags.2?Vector<StarGiftAttributeCounter> users:Vector<User> = payments.ResaleStarGifts;
|
||||
stories.canSendStoryCount#c387c04e count_remains:int = stories.CanSendStoryCount;
|
||||
pendingSuggestion#e7e82e12 suggestion:string title:TextWithEntities description:TextWithEntities url:string = PendingSuggestion;
|
||||
todoItem#cba9a52f id:int title:TextWithEntities = TodoItem;
|
||||
todoList#49b92a26 flags:# others_can_append:flags.0?true others_can_complete:flags.1?true title:TextWithEntities list:Vector<TodoItem> = TodoList;
|
||||
todoCompletion#4cc120b7 id:int completed_by:long date:int = TodoCompletion;
|
||||
---functions---
|
||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||
initConnection#c1cd5ea9 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy params:flags.1?JSONValue query:!X = X;
|
||||
@ -1503,8 +1510,7 @@ account.toggleUsername#58d6b376 username:string active:Bool = Bool;
|
||||
account.resolveBusinessChatLink#5492e5ee slug:string = account.ResolvedBusinessChatLinks;
|
||||
account.toggleSponsoredMessages#b9d9a38d enabled:Bool = Bool;
|
||||
account.getCollectibleEmojiStatuses#2e7b4543 hash:long = account.EmojiStatuses;
|
||||
account.addNoPaidMessagesException#6f688aa7 flags:# refund_charged:flags.0?true user_id:InputUser = Bool;
|
||||
account.getPaidMessagesRevenue#f1266f38 user_id:InputUser = account.PaidMessagesRevenue;
|
||||
account.getPaidMessagesRevenue#19ba4a67 flags:# parent_peer:flags.0?InputPeer user_id:InputUser = account.PaidMessagesRevenue;
|
||||
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
|
||||
users.getFullUser#b60f5918 id:InputUser = users.UserFull;
|
||||
contacts.getContacts#5dd69e12 hash:long = contacts.Contacts;
|
||||
@ -1663,9 +1669,11 @@ messages.getPaidReactionPrivacy#472455aa = Updates;
|
||||
messages.viewSponsoredMessage#269e3643 random_id:bytes = Bool;
|
||||
messages.clickSponsoredMessage#8235057e flags:# media:flags.0?true fullscreen:flags.1?true random_id:bytes = Bool;
|
||||
messages.reportSponsoredMessage#12cbf0c4 random_id:bytes option:bytes = channels.SponsoredMessageReportResult;
|
||||
messages.getSponsoredMessages#9bd2f439 peer:InputPeer = messages.SponsoredMessages;
|
||||
messages.getSponsoredMessages#3d6ce850 flags:# peer:InputPeer msg_id:flags.0?int = messages.SponsoredMessages;
|
||||
messages.getPreparedInlineMessage#857ebdb8 bot:InputUser id:string = messages.PreparedInlineMessage;
|
||||
messages.reportMessagesDelivery#5a6d7395 flags:# push:flags.0?true peer:InputPeer id:Vector<int> = Bool;
|
||||
messages.toggleTodoCompleted#d3e03124 peer:InputPeer msg_id:int completed:Vector<int> incompleted:Vector<int> = Updates;
|
||||
messages.appendTodoList#21a61057 peer:InputPeer msg_id:int list:Vector<TodoItem> = Updates;
|
||||
updates.getState#edd4882a = updates.State;
|
||||
updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference;
|
||||
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;
|
||||
|
||||
@ -61,7 +61,7 @@
|
||||
"account.resolveBusinessChatLink",
|
||||
"account.toggleSponsoredMessages",
|
||||
"account.getCollectibleEmojiStatuses",
|
||||
"account.addNoPaidMessagesException",
|
||||
"account.toggleNoPaidMessagesException ",
|
||||
"account.getPaidMessagesRevenue",
|
||||
"account.getAccountTTL",
|
||||
"account.setAccountTTL",
|
||||
@ -225,6 +225,8 @@
|
||||
"messages.getSponsoredMessages",
|
||||
"messages.reportMessagesDelivery",
|
||||
"messages.getPreparedInlineMessage",
|
||||
"messages.toggleTodoCompleted",
|
||||
"messages.appendTodoList",
|
||||
"updates.getState",
|
||||
"updates.getDifference",
|
||||
"updates.getChannelDifference",
|
||||
|
||||
@ -46,6 +46,7 @@ inputMediaDice#e66fbf7b emoticon:string = InputMedia;
|
||||
inputMediaStory#89fdd778 peer:InputPeer id:int = InputMedia;
|
||||
inputMediaWebPage#c21b8849 flags:# force_large_media:flags.0?true force_small_media:flags.1?true optional:flags.2?true url:string = InputMedia;
|
||||
inputMediaPaidMedia#c4103386 flags:# stars_amount:long extended_media:Vector<InputMedia> payload:flags.0?string = InputMedia;
|
||||
inputMediaTodo#9fc55fde todo:TodoList = InputMedia;
|
||||
|
||||
inputChatPhotoEmpty#1ca48f57 = InputChatPhoto;
|
||||
inputChatUploadedPhoto#bdcdaec0 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.3?VideoSize = InputChatPhoto;
|
||||
@ -103,7 +104,7 @@ channel#fe685355 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.
|
||||
channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
|
||||
|
||||
chatFull#2633421b flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector<BotInfo> pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector<long> available_reactions:flags.18?ChatReactions reactions_limit:flags.20?int = ChatFull;
|
||||
channelFull#52d6806b flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true paid_messages_available:flags2.20?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int = ChatFull;
|
||||
channelFull#e07429de flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true stories_pinned_available:flags2.5?true view_forum_as_messages:flags2.6?true restricted_sponsored:flags2.11?true can_view_revenue:flags2.12?true paid_media_allowed:flags2.14?true can_view_stars_revenue:flags2.15?true paid_reactions_available:flags2.16?true stargifts_available:flags2.19?true paid_messages_available:flags2.20?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector<BotInfo> migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector<string> groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector<long> default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions reactions_limit:flags2.13?int stories:flags2.4?PeerStories wallpaper:flags2.7?WallPaper boosts_applied:flags2.8?int boosts_unrestrict:flags2.9?int emojiset:flags2.10?StickerSet bot_verification:flags2.17?BotVerification stargifts_count:flags2.18?int send_paid_messages_stars:flags2.21?long = ChatFull;
|
||||
|
||||
chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant;
|
||||
chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant;
|
||||
@ -117,7 +118,7 @@ chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:f
|
||||
|
||||
messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message;
|
||||
message#eabcdd4d flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true flags2:# offline:flags2.1?true video_processing_pending:flags2.4?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer saved_peer_id:flags.28?Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long via_business_bot_id:flags2.0?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector<RestrictionReason> ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int effect:flags2.2?long factcheck:flags2.3?FactCheck report_delivery_until_date:flags2.5?int paid_message_stars:flags2.6?long = Message;
|
||||
messageService#d3d28540 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true reactions_are_possible:flags.9?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction reactions:flags.20?MessageReactions ttl_period:flags.25?int = Message;
|
||||
messageService#7a800e0a flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true reactions_are_possible:flags.9?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer saved_peer_id:flags.28?Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction reactions:flags.20?MessageReactions ttl_period:flags.25?int = Message;
|
||||
|
||||
messageMediaEmpty#3ded6320 = MessageMedia;
|
||||
messageMediaPhoto#695150d7 flags:# spoiler:flags.3?true photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia;
|
||||
@ -136,6 +137,7 @@ messageMediaStory#68cb6283 flags:# via_mention:flags.1?true peer:Peer id:int sto
|
||||
messageMediaGiveaway#aa073beb flags:# only_new_subscribers:flags.0?true winners_are_visible:flags.2?true channels:Vector<long> countries_iso2:flags.1?Vector<string> prize_description:flags.3?string quantity:int months:flags.4?int stars:flags.5?long until_date:int = MessageMedia;
|
||||
messageMediaGiveawayResults#ceaa3ea1 flags:# only_new_subscribers:flags.0?true refunded:flags.2?true channel_id:long additional_peers_count:flags.3?int launch_msg_id:int winners_count:int unclaimed_count:int winners:Vector<long> months:flags.4?int stars:flags.5?long prize_description:flags.1?string until_date:int = MessageMedia;
|
||||
messageMediaPaidMedia#a8852491 stars_amount:long extended_media:Vector<MessageExtendedMedia> = MessageMedia;
|
||||
messageMediaToDo#8a53b014 flags:# todo:TodoList completions:flags.0?Vector<TodoCompletion> = MessageMedia;
|
||||
|
||||
messageActionEmpty#b6aef7b0 = MessageAction;
|
||||
messageActionChatCreate#bd47cbad title:string users:Vector<long> = MessageAction;
|
||||
@ -188,6 +190,8 @@ messageActionStarGiftUnique#2e3ae60e flags:# upgrade:flags.0?true transferred:fl
|
||||
messageActionPaidMessagesRefunded#ac1f1fcd count:int stars:long = MessageAction;
|
||||
messageActionPaidMessagesPrice#84b88578 flags:# broadcast_messages_allowed:flags.0?true stars:long = MessageAction;
|
||||
messageActionConferenceCall#2ffe2f7a flags:# missed:flags.0?true active:flags.1?true video:flags.4?true call_id:long duration:flags.2?int other_participants:flags.3?Vector<Peer> = MessageAction;
|
||||
messageActionTodoCompletions#cc7c5c89 completed:Vector<int> incompleted:Vector<int> = MessageAction;
|
||||
messageActionTodoAppendTasks#c7edbc83 list:Vector<TodoItem> = MessageAction;
|
||||
|
||||
dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true view_forum_as_messages:flags.6?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog;
|
||||
dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
|
||||
@ -1407,9 +1411,9 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR
|
||||
account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult;
|
||||
account.resetPasswordOk#e926d63e = account.ResetPasswordResult;
|
||||
|
||||
sponsoredMessage#4d93a990 flags:# recommended:flags.5?true can_report:flags.12?true random_id:bytes url:string title:string message:string entities:flags.1?Vector<MessageEntity> photo:flags.6?Photo media:flags.14?MessageMedia color:flags.13?PeerColor button_text:string sponsor_info:flags.7?string additional_info:flags.8?string = SponsoredMessage;
|
||||
sponsoredMessage#7dbf8673 flags:# recommended:flags.5?true can_report:flags.12?true random_id:bytes url:string title:string message:string entities:flags.1?Vector<MessageEntity> photo:flags.6?Photo media:flags.14?MessageMedia color:flags.13?PeerColor button_text:string sponsor_info:flags.7?string additional_info:flags.8?string min_display_duration:flags.15?int max_display_duration:flags.15?int = SponsoredMessage;
|
||||
|
||||
messages.sponsoredMessages#c9ee1d87 flags:# posts_between:flags.0?int messages:Vector<SponsoredMessage> chats:Vector<Chat> users:Vector<User> = messages.SponsoredMessages;
|
||||
messages.sponsoredMessages#ffda656d flags:# posts_between:flags.0?int start_delay:flags.1?int between_delay:flags.2?int messages:Vector<SponsoredMessage> chats:Vector<Chat> users:Vector<User> = messages.SponsoredMessages;
|
||||
messages.sponsoredMessagesEmpty#1839490f = messages.SponsoredMessages;
|
||||
|
||||
searchResultsCalendarPeriod#c9b0539f date:int min_msg_id:int max_msg_id:int count:int = SearchResultsCalendarPeriod;
|
||||
@ -1715,7 +1719,7 @@ storyReactionPublicRepost#cfcd0f13 peer_id:Peer story:StoryItem = StoryReaction;
|
||||
stories.storyReactionsList#aa5f789c flags:# count:int reactions:Vector<StoryReaction> chats:Vector<Chat> users:Vector<User> next_offset:flags.0?string = stories.StoryReactionsList;
|
||||
|
||||
savedDialog#bd87cb6c flags:# pinned:flags.2?true peer:Peer top_message:int = SavedDialog;
|
||||
monoForumDialog#64407ea7 flags:# unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_reactions_count:int draft:flags.1?DraftMessage = SavedDialog;
|
||||
monoForumDialog#64407ea7 flags:# unread_mark:flags.3?true nopaid_messages_exception:flags.4?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_reactions_count:int draft:flags.1?DraftMessage = SavedDialog;
|
||||
|
||||
messages.savedDialogs#f83ae221 dialogs:Vector<SavedDialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.SavedDialogs;
|
||||
messages.savedDialogsSlice#44ba9dd9 count:int dialogs:Vector<SavedDialog> messages:Vector<Message> chats:Vector<Chat> users:Vector<User> = messages.SavedDialogs;
|
||||
@ -1983,6 +1987,12 @@ stories.canSendStoryCount#c387c04e count_remains:int = stories.CanSendStoryCount
|
||||
|
||||
pendingSuggestion#e7e82e12 suggestion:string title:TextWithEntities description:TextWithEntities url:string = PendingSuggestion;
|
||||
|
||||
todoItem#cba9a52f id:int title:TextWithEntities = TodoItem;
|
||||
|
||||
todoList#49b92a26 flags:# others_can_append:flags.0?true others_can_complete:flags.1?true title:TextWithEntities list:Vector<TodoItem> = TodoList;
|
||||
|
||||
todoCompletion#4cc120b7 id:int completed_by:long date:int = TodoCompletion;
|
||||
|
||||
---functions---
|
||||
|
||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||
@ -2134,8 +2144,8 @@ account.toggleSponsoredMessages#b9d9a38d enabled:Bool = Bool;
|
||||
account.getReactionsNotifySettings#6dd654c = ReactionsNotifySettings;
|
||||
account.setReactionsNotifySettings#316ce548 settings:ReactionsNotifySettings = ReactionsNotifySettings;
|
||||
account.getCollectibleEmojiStatuses#2e7b4543 hash:long = account.EmojiStatuses;
|
||||
account.addNoPaidMessagesException#6f688aa7 flags:# refund_charged:flags.0?true user_id:InputUser = Bool;
|
||||
account.getPaidMessagesRevenue#f1266f38 user_id:InputUser = account.PaidMessagesRevenue;
|
||||
account.getPaidMessagesRevenue#19ba4a67 flags:# parent_peer:flags.0?InputPeer user_id:InputUser = account.PaidMessagesRevenue;
|
||||
account.toggleNoPaidMessagesException#fe2eda76 flags:# refund_charged:flags.0?true require_payment:flags.2?true parent_peer:flags.1?InputPeer user_id:InputUser = Bool;
|
||||
|
||||
users.getUsers#d91a548 id:Vector<InputUser> = Vector<User>;
|
||||
users.getFullUser#b60f5918 id:InputUser = users.UserFull;
|
||||
@ -2390,13 +2400,15 @@ messages.getPaidReactionPrivacy#472455aa = Updates;
|
||||
messages.viewSponsoredMessage#269e3643 random_id:bytes = Bool;
|
||||
messages.clickSponsoredMessage#8235057e flags:# media:flags.0?true fullscreen:flags.1?true random_id:bytes = Bool;
|
||||
messages.reportSponsoredMessage#12cbf0c4 random_id:bytes option:bytes = channels.SponsoredMessageReportResult;
|
||||
messages.getSponsoredMessages#9bd2f439 peer:InputPeer = messages.SponsoredMessages;
|
||||
messages.getSponsoredMessages#3d6ce850 flags:# peer:InputPeer msg_id:flags.0?int = messages.SponsoredMessages;
|
||||
messages.savePreparedInlineMessage#f21f7f2f flags:# result:InputBotInlineResult user_id:InputUser peer_types:flags.0?Vector<InlineQueryPeerType> = messages.BotPreparedInlineMessage;
|
||||
messages.getPreparedInlineMessage#857ebdb8 bot:InputUser id:string = messages.PreparedInlineMessage;
|
||||
messages.searchStickers#29b1c66a flags:# emojis:flags.0?true q:string emoticon:string lang_code:Vector<string> offset:int limit:int hash:long = messages.FoundStickers;
|
||||
messages.reportMessagesDelivery#5a6d7395 flags:# push:flags.0?true peer:InputPeer id:Vector<int> = Bool;
|
||||
messages.getSavedDialogsByID#6f6f9c96 flags:# parent_peer:flags.1?InputPeer ids:Vector<InputPeer> = messages.SavedDialogs;
|
||||
messages.readSavedHistory#ba4a3b5b parent_peer:InputPeer peer:InputPeer max_id:int = Bool;
|
||||
messages.toggleTodoCompleted#d3e03124 peer:InputPeer msg_id:int completed:Vector<int> incompleted:Vector<int> = Updates;
|
||||
messages.appendTodoList#21a61057 peer:InputPeer msg_id:int list:Vector<TodoItem> = Updates;
|
||||
|
||||
updates.getState#edd4882a = updates.State;
|
||||
updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference;
|
||||
|
||||
@ -21,6 +21,7 @@ import type {
|
||||
ApiMediaFormat,
|
||||
ApiMessage,
|
||||
ApiMessageEntity,
|
||||
ApiNewMediaTodo,
|
||||
ApiNewPoll,
|
||||
ApiPeer,
|
||||
ApiPhoto,
|
||||
@ -730,6 +731,7 @@ export type SendMessageParams = {
|
||||
story?: ApiStory | ApiStorySkipped;
|
||||
gif?: ApiVideo;
|
||||
poll?: ApiNewPoll;
|
||||
todo?: ApiNewMediaTodo;
|
||||
contact?: ApiContact;
|
||||
isSilent?: boolean;
|
||||
scheduledAt?: number;
|
||||
|
||||
81
src/types/language.d.ts
vendored
81
src/types/language.d.ts
vendored
@ -50,6 +50,7 @@ export interface LangPair {
|
||||
'PremiumPreviewVoiceToTextDescription': undefined;
|
||||
'PremiumPreviewProfileBadgeDescription': undefined;
|
||||
'PremiumPreviewDownloadSpeedDescription': undefined;
|
||||
'PremiumPreviewTodoDescription': undefined;
|
||||
'PremiumPreviewUploadsDescription': undefined;
|
||||
'PremiumPreviewAdvancedChatManagementDescription': undefined;
|
||||
'PremiumPreviewAnimatedProfilesDescription': undefined;
|
||||
@ -1537,6 +1538,27 @@ export interface LangPair {
|
||||
'MonoforumComposerPlaceholder': undefined;
|
||||
'ChannelSendMessage': undefined;
|
||||
'AutomaticTranslation': undefined;
|
||||
'TitleNewToDoList': undefined;
|
||||
'TitleEditToDoList': undefined;
|
||||
'TitleAppendToDoList': undefined;
|
||||
'InputTitle': undefined;
|
||||
'TitleToDoList': undefined;
|
||||
'TitleTask': undefined;
|
||||
'TitleAddTask': undefined;
|
||||
'AllowOthersAddTasks': undefined;
|
||||
'AllowOthersMarkAsDone': undefined;
|
||||
'AriaToDoCancel': undefined;
|
||||
'TitleGroupToDoList': undefined;
|
||||
'TitleYourToDoList': undefined;
|
||||
'ToDoListNewTasks': undefined;
|
||||
'MenuButtonAppendTodoList': undefined;
|
||||
'PremiumMore': undefined;
|
||||
'SubscribeToTelegramPremiumForToggleTask': undefined;
|
||||
'SubscribeToTelegramPremiumForCreateToDo': undefined;
|
||||
'SubscribeToTelegramPremiumForAppendToDo': undefined;
|
||||
'ToDoListErrorChooseTitle': undefined;
|
||||
'ToDoListErrorChooseTasks': undefined;
|
||||
'PremiumPreviewTodo': undefined;
|
||||
}
|
||||
|
||||
export interface LangPairWithVariables<V = LangVariable> {
|
||||
@ -2518,6 +2540,62 @@ export interface LangPairWithVariables<V = LangVariable> {
|
||||
'ComposerTitleForwardFrom': {
|
||||
'users': V;
|
||||
};
|
||||
'TitleUserToDoList': {
|
||||
'peer': V;
|
||||
};
|
||||
'DescriptionCompletedToDoTasks': {
|
||||
'number': V;
|
||||
'count': V;
|
||||
};
|
||||
'MessageActionTodoCompletionsAsDone': {
|
||||
'peer': V;
|
||||
'task': V;
|
||||
};
|
||||
'MessageActionTodoCompletionsAsDoneYou': {
|
||||
'task': V;
|
||||
};
|
||||
'MessageActionTodoCompletionsAsDoneMultiple': {
|
||||
'peer': V;
|
||||
'tasks': V;
|
||||
};
|
||||
'MessageActionTodoCompletionsAsDoneMultipleYou': {
|
||||
'tasks': V;
|
||||
};
|
||||
'MessageActionTodoCompletionsAsNotDone': {
|
||||
'peer': V;
|
||||
'task': V;
|
||||
};
|
||||
'MessageActionTodoCompletionsAsNotDoneYou': {
|
||||
'task': V;
|
||||
};
|
||||
'MessageActionTodoCompletionsAsNotDoneMultiple': {
|
||||
'peer': V;
|
||||
'tasks': V;
|
||||
};
|
||||
'MessageActionTodoCompletionsAsNotDoneMultipleYou': {
|
||||
'tasks': V;
|
||||
};
|
||||
'MessageActionAppendTodo': {
|
||||
'peer': V;
|
||||
'task': V;
|
||||
'list': V;
|
||||
};
|
||||
'MessageActionAppendTodoYou': {
|
||||
'task': V;
|
||||
'list': V;
|
||||
};
|
||||
'MessageActionAppendTodoMultiple': {
|
||||
'peer': V;
|
||||
'tasks': V;
|
||||
'list': V;
|
||||
};
|
||||
'MessageActionAppendTodoMultipleYou': {
|
||||
'tasks': V;
|
||||
'list': V;
|
||||
};
|
||||
'HintTodoListTasksCount': {
|
||||
'count': V;
|
||||
};
|
||||
}
|
||||
|
||||
export interface LangPairPlural {
|
||||
@ -2830,6 +2908,9 @@ export interface LangPairPluralWithVariables<V = LangVariable> {
|
||||
'GiftAttributeSymbolPlural': {
|
||||
'count': V;
|
||||
};
|
||||
'MessageActionTodoTaskCount': {
|
||||
'count': V;
|
||||
};
|
||||
}
|
||||
export type RegularLangKey = keyof LangPair;
|
||||
export type RegularLangKeyWithVariables = keyof LangPairWithVariables;
|
||||
|
||||
@ -1,3 +1,9 @@
|
||||
export default function generateUniqueId() {
|
||||
return Date.now().toString(36) + Math.random().toString(36).slice(2);
|
||||
}
|
||||
|
||||
export function generateUniqueNumberId() {
|
||||
const timestamp = Date.now() % 100000000;
|
||||
const random = Math.floor(Math.random() * 1000);
|
||||
return timestamp + random;
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user