From bc4c892880b9c6c72506f345177de447091d6671 Mon Sep 17 00:00:00 2001 From: morethanwords Date: Sat, 10 Oct 2020 01:36:06 +0300 Subject: [PATCH] MTProto autocomplete flags Chat context menu fixes Revote & stop poll from context menu --- src/components/buttonMenu.ts | 30 ++ src/components/chat/contextMenu.ts | 304 +++++++++++------- src/components/chat/input.ts | 28 +- src/components/misc.ts | 4 +- src/components/popupCreatePoll.ts | 36 +-- src/components/sidebarLeft/tabs/editFolder.ts | 11 +- src/index.hbs | 8 - src/lib/appManagers/AppInlineBotsManager.ts | 20 +- src/lib/appManagers/apiUpdatesManager.ts | 10 +- src/lib/appManagers/appChatsManager.ts | 2 +- src/lib/appManagers/appDocsManager.ts | 13 +- src/lib/appManagers/appImManager.ts | 82 +++-- src/lib/appManagers/appMessagesManager.ts | 263 +++++---------- src/lib/appManagers/appPhotosManager.ts | 11 +- src/lib/appManagers/appPollsManager.ts | 36 ++- src/lib/appManagers/appStickersManager.ts | 12 +- src/lib/mtproto/passwordManager.ts | 6 +- src/lib/mtproto/schema.ts | 12 +- src/lib/mtproto/tl_utils.ts | 135 ++++---- src/lib/utils.ts | 9 +- src/pages/pageAuthCode.ts | 16 +- src/pages/pageSignIn.ts | 16 +- 22 files changed, 534 insertions(+), 530 deletions(-) create mode 100644 src/components/buttonMenu.ts diff --git a/src/components/buttonMenu.ts b/src/components/buttonMenu.ts new file mode 100644 index 00000000..d0229067 --- /dev/null +++ b/src/components/buttonMenu.ts @@ -0,0 +1,30 @@ +import { ripple } from "./ripple"; + +export type ButtonMenuItemOptions = {icon: string, text: string, onClick: () => void, element?: HTMLElement}; + +const ButtonMenuItem = (options: ButtonMenuItemOptions) => { + if(options.element) return options.element; + + const {icon, text, onClick} = options; + const el = document.createElement('div'); + el.className = 'btn-menu-item tgico-' + icon; + el.innerText = text; + + ripple(el); + el.addEventListener('click', onClick); + + return options.element = el; +}; + +const ButtonMenu = (buttons: ButtonMenuItemOptions[]) => { + const el = document.createElement('div'); + el.classList.add('btn-menu'); + + const items = buttons.map(ButtonMenuItem); + + el.append(...items); + + return el; +}; + +export default ButtonMenu; \ No newline at end of file diff --git a/src/components/chat/contextMenu.ts b/src/components/chat/contextMenu.ts index 3ec80ce1..c9404070 100644 --- a/src/components/chat/contextMenu.ts +++ b/src/components/chat/contextMenu.ts @@ -2,28 +2,26 @@ import appChatsManager from "../../lib/appManagers/appChatsManager"; import appImManager from "../../lib/appManagers/appImManager"; import appMessagesManager from "../../lib/appManagers/appMessagesManager"; import appPeersManager from "../../lib/appManagers/appPeersManager"; +import appPollsManager from "../../lib/appManagers/appPollsManager"; import $rootScope from "../../lib/rootScope"; import { findUpClassName } from "../../lib/utils"; -import { attachContextMenuListener, openBtnMenu, parseMenuButtonsTo, positionMenu } from "../misc"; +import ButtonMenu, { ButtonMenuItemOptions } from "../buttonMenu"; +import { attachContextMenuListener, openBtnMenu, positionMenu } from "../misc"; import { PopupButton, PopupPeer } from "../popup"; import appSidebarRight from "../sidebarRight"; export class ChatContextMenu { - private element = document.getElementById('bubble-contextmenu') as HTMLDivElement; - private buttons: { - reply: HTMLButtonElement, - edit: HTMLButtonElement, - copy: HTMLButtonElement, - pin: HTMLButtonElement, - forward: HTMLButtonElement, - delete: HTMLButtonElement - } = {} as any; + private buttons: (ButtonMenuItemOptions & {verify: (peerID: number, msgID: number) => boolean})[]; + private element: HTMLElement; public msgID: number; constructor(private attachTo: HTMLElement) { - parseMenuButtonsTo(this.buttons, this.element.children); - attachContextMenuListener(attachTo, (e) => { + if(this.init) { + this.init(); + this.init = null; + } + let bubble: HTMLElement = null; try { @@ -40,126 +38,186 @@ export class ChatContextMenu { bubble = bubble.parentElement as HTMLDivElement; // bc container - let msgID = +bubble.dataset.mid; + const msgID = +bubble.dataset.mid; if(!msgID) return; - let peerID = $rootScope.selectedPeerID; + const peerID = $rootScope.selectedPeerID; this.msgID = msgID; - const message = appMessagesManager.getMessage(msgID); + this.buttons.forEach(button => { + const good = button.verify(peerID, msgID); + button.element.classList.toggle('hide', !good); + }); - this.buttons.copy.style.display = message.message ? '' : 'none'; - - this.buttons.pin.classList.toggle('hide', peerID < 0 && !appChatsManager.hasRights(-peerID, 'pin')); - this.buttons.edit.style.display = appMessagesManager.canEditMessage(msgID) ? '' : 'none'; - this.buttons.reply.classList.toggle('hide', peerID < 0 && !appChatsManager.hasRights(-peerID, 'send')); - this.buttons.delete.classList.toggle('hide', peerID < 0 && appPeersManager.isBroadcast(peerID) && !appChatsManager.hasRights(-peerID, 'deleteRevoke')); - - let side: 'left' | 'right' = bubble.classList.contains('is-in') ? 'left' : 'right'; + const side: 'left' | 'right' = bubble.classList.contains('is-in') ? 'left' : 'right'; positionMenu(e, this.element, side); openBtnMenu(this.element); /////this.log('contextmenu', e, bubble, msgID, side); }); - - this.buttons.copy.addEventListener('click', () => { - let message = appMessagesManager.getMessage(this.msgID); - - let str = message ? message.message : ''; - - var textArea = document.createElement("textarea"); - textArea.value = str; - textArea.style.position = "fixed"; //avoid scrolling to bottom - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - - try { - document.execCommand('copy'); - } catch (err) { - console.error('Oops, unable to copy', err); - } - - document.body.removeChild(textArea); - }); - - this.buttons.delete.addEventListener('click', () => { - let peerID = $rootScope.selectedPeerID; - let firstName = appPeersManager.getPeerTitle(peerID, false, true); - - let callback = (revoke: boolean) => { - appMessagesManager.deleteMessages([this.msgID], revoke); - }; - - let title: string, description: string, buttons: PopupButton[]; - title = 'Delete Message?'; - description = `Are you sure you want to delete this message?`; - - if(peerID == $rootScope.myID) { - buttons = [{ - text: 'DELETE', - isDanger: true, - callback: () => callback(false) - }]; - } else { - buttons = [{ - text: 'DELETE JUST FOR ME', - isDanger: true, - callback: () => callback(false) - }]; - - if(peerID > 0) { - buttons.push({ - text: 'DELETE FOR ME AND ' + firstName, - isDanger: true, - callback: () => callback(true) - }); - } else if(appChatsManager.hasRights(-peerID, 'deleteRevoke')) { - buttons.push({ - text: 'DELETE FOR ALL', - isDanger: true, - callback: () => callback(true) - }); - } - } - - buttons.push({ - text: 'CANCEL', - isCancel: true - }); - - let popup = new PopupPeer('popup-delete-chat', { - peerID: peerID, - title: title, - description: description, - buttons: buttons - }); - - popup.show(); - }); - - this.buttons.reply.addEventListener('click', () => { - const message = appMessagesManager.getMessage(this.msgID); - const chatInputC = appImManager.chatInputC; - chatInputC.setTopInfo(appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message); - chatInputC.replyToMsgID = this.msgID; - chatInputC.editMsgID = 0; - }); - - this.buttons.forward.addEventListener('click', () => { - appSidebarRight.forwardTab.open([this.msgID]); - }); - - this.buttons.edit.addEventListener('click', () => { - const message = appMessagesManager.getMessage(this.msgID); - const chatInputC = appImManager.chatInputC; - chatInputC.setTopInfo('Editing', message.message, message.message, message); - chatInputC.replyToMsgID = 0; - chatInputC.editMsgID = this.msgID; - }); - - this.buttons.pin.addEventListener('click', () => { - appMessagesManager.updatePinnedMessage($rootScope.selectedPeerID, this.msgID); - }); } + + private init = () => { + this.buttons = [{ + icon: 'reply', + text: 'Reply', + onClick: this.onReplyClick, + verify: (peerID: number) => peerID > 0 || appChatsManager.hasRights(-peerID, 'send') + }, { + icon: 'edit', + text: 'Edit', + onClick: this.onEditClick, + verify: (peerID: number, msgID: number) => appMessagesManager.canEditMessage(msgID) + }, { + icon: 'copy', + text: 'Copy', + onClick: this.onCopyClick, + verify: (peerID: number, msgID: number) => !!appMessagesManager.getMessage(msgID).message + }, { + icon: 'pin', + text: 'Pin', + onClick: this.onPinClick, + verify: (peerID: number) => peerID == $rootScope.myID || (peerID < 0 && appChatsManager.hasRights(-peerID, 'pin')) + }, { + icon: 'revote', + text: 'Revote', + onClick: this.onRetractVote, + verify: (peerID: number, msgID) => { + const message = appMessagesManager.getMessage(msgID); + const poll = message.media?.poll; + return poll && !poll.pFlags.closed; + } + }, { + icon: 'lock', + text: 'Stop poll', + onClick: this.onStopPoll, + verify: (peerID: number, msgID) => { + const message = appMessagesManager.getMessage(msgID); + const poll = message.media?.poll; + return appMessagesManager.canEditMessage(msgID) && message.fromID == $rootScope.myID && message.fwd_from === undefined && poll && !poll.pFlags.closed; + } + }, { + icon: 'forward', + text: 'Forward', + onClick: this.onForwardClick, + verify: () => true + }, { + icon: 'delete danger', + text: 'Delete', + onClick: this.onDeleteClick, + verify: (peerID: number, msgID: number) => peerID > 0 || appMessagesManager.getMessage(msgID).fromID == $rootScope.myID || appChatsManager.hasRights(-peerID, 'deleteRevoke') + }]; + + this.element = ButtonMenu(this.buttons); + this.element.id = 'bubble-contextmenu'; + appImManager.chatInput.parentElement.insertBefore(this.element, appImManager.chatInput); + }; + + private onReplyClick = () => { + const message = appMessagesManager.getMessage(this.msgID); + const chatInputC = appImManager.chatInputC; + chatInputC.setTopInfo(appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message); + chatInputC.replyToMsgID = this.msgID; + chatInputC.editMsgID = 0; + }; + + private onEditClick = () => { + const message = appMessagesManager.getMessage(this.msgID); + const chatInputC = appImManager.chatInputC; + chatInputC.setTopInfo('Editing', message.message, message.message, message); + chatInputC.replyToMsgID = 0; + chatInputC.editMsgID = this.msgID; + }; + + private onCopyClick = () => { + let message = appMessagesManager.getMessage(this.msgID); + + let str = message ? message.message : ''; + + var textArea = document.createElement("textarea"); + textArea.value = str; + textArea.style.position = "fixed"; //avoid scrolling to bottom + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + try { + document.execCommand('copy'); + } catch (err) { + console.error('Oops, unable to copy', err); + } + + document.body.removeChild(textArea); + }; + + private onPinClick = () => { + appMessagesManager.updatePinnedMessage($rootScope.selectedPeerID, this.msgID); + }; + + private onRetractVote = () => { + appPollsManager.sendVote(this.msgID, []); + }; + + private onStopPoll = () => { + appPollsManager.stopPoll(this.msgID); + }; + + private onForwardClick = () => { + appSidebarRight.forwardTab.open([this.msgID]); + }; + + private onDeleteClick = () => { + let peerID = $rootScope.selectedPeerID; + let firstName = appPeersManager.getPeerTitle(peerID, false, true); + + let callback = (revoke: boolean) => { + appMessagesManager.deleteMessages([this.msgID], revoke); + }; + + let title: string, description: string, buttons: PopupButton[]; + title = 'Delete Message?'; + description = `Are you sure you want to delete this message?`; + + if(peerID == $rootScope.myID) { + buttons = [{ + text: 'DELETE', + isDanger: true, + callback: () => callback(false) + }]; + } else { + buttons = [{ + text: 'DELETE JUST FOR ME', + isDanger: true, + callback: () => callback(false) + }]; + + if(peerID > 0) { + buttons.push({ + text: 'DELETE FOR ME AND ' + firstName, + isDanger: true, + callback: () => callback(true) + }); + } else if(appChatsManager.hasRights(-peerID, 'deleteRevoke')) { + buttons.push({ + text: 'DELETE FOR ALL', + isDanger: true, + callback: () => callback(true) + }); + } + } + + buttons.push({ + text: 'CANCEL', + isCancel: true + }); + + let popup = new PopupPeer('popup-delete-chat', { + peerID: peerID, + title: title, + description: description, + buttons: buttons + }); + + popup.show(); + }; } \ No newline at end of file diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index 74d1e854..7e4ec0da 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -1,20 +1,20 @@ -import Scrollable from "../scrollable"; -import { RichTextProcessor } from "../../lib/richtextprocessor"; -import apiManager from "../../lib/mtproto/mtprotoworker"; -import appWebPagesManager from "../../lib/appManagers/appWebPagesManager"; -import appImManager from "../../lib/appManagers/appImManager"; -import { getRichValue, calcImageInBox, cancelEvent } from "../../lib/utils"; -import { wrapDocument, wrapReply } from "../wrappers"; -import appMessagesManager from "../../lib/appManagers/appMessagesManager"; -import { Layouter, RectPart } from "../groupedLayout"; import Recorder from '../../../public/recorder.min'; +import { isTouchSupported } from "../../helpers/touchSupport"; +import appDocsManager from "../../lib/appManagers/appDocsManager"; +import appImManager from "../../lib/appManagers/appImManager"; +import appMessagesManager from "../../lib/appManagers/appMessagesManager"; +import appWebPagesManager from "../../lib/appManagers/appWebPagesManager"; +import apiManager from "../../lib/mtproto/mtprotoworker"; //import Recorder from '../opus-recorder/dist/recorder.min'; import opusDecodeController from "../../lib/opusDecodeController"; -import appDocsManager from "../../lib/appManagers/appDocsManager"; +import { RichTextProcessor } from "../../lib/richtextprocessor"; +import { calcImageInBox, cancelEvent, getRichValue } from "../../lib/utils"; import emoticonsDropdown from "../emoticonsDropdown"; +import { Layouter, RectPart } from "../groupedLayout"; import PopupCreatePoll from "../popupCreatePoll"; +import Scrollable from "../scrollable"; import { toast } from "../toast"; -import { isTouchSupported } from "../../helpers/touchSupport"; +import { wrapDocument, wrapReply } from "../wrappers"; const RECORD_MIN_TIME = 500; @@ -57,7 +57,7 @@ export class ChatInput { public willSendWebPage: any = null; public replyToMsgID = 0; public editMsgID = 0; - public noWebPage = false; + public noWebPage: true; private recorder: any; private recording = false; @@ -159,7 +159,7 @@ export class ChatInput { this.setTopInfo(webpage.site_name || webpage.title, webpage.description || webpage.url); this.replyToMsgID = 0; - this.noWebPage = false; + delete this.noWebPage; this.willSendWebPage = webpage; } }); @@ -744,7 +744,7 @@ export class ChatInput { if(clearInput) { this.lastUrl = ''; this.editMsgID = 0; - this.noWebPage = false; + delete this.noWebPage; this.willSendWebPage = null; this.messageInput.innerText = ''; diff --git a/src/components/misc.ts b/src/components/misc.ts index 6fb09187..2aecde75 100644 --- a/src/components/misc.ts +++ b/src/components/misc.ts @@ -171,8 +171,8 @@ window.addEventListener('resize', () => { } */ }); -let openedMenu: HTMLDivElement = null, openedMenuOnClose: () => void = null; -export function openBtnMenu(menuElement: HTMLDivElement, onClose?: () => void) { +let openedMenu: HTMLElement = null, openedMenuOnClose: () => void = null; +export function openBtnMenu(menuElement: HTMLElement, onClose?: () => void) { closeBtnMenu(); openedMenu = menuElement; diff --git a/src/components/popupCreatePoll.ts b/src/components/popupCreatePoll.ts index f8d83b3c..011a136d 100644 --- a/src/components/popupCreatePoll.ts +++ b/src/components/popupCreatePoll.ts @@ -1,8 +1,8 @@ +import appMessagesManager from "../lib/appManagers/appMessagesManager"; +import appPollsManager, { Poll } from "../lib/appManagers/appPollsManager"; +import $rootScope from "../lib/rootScope"; import { PopupElement } from "./popup"; import Scrollable from "./scrollable"; -import appMessagesManager from "../lib/appManagers/appMessagesManager"; -import $rootScope from "../lib/rootScope"; -import { Poll } from "../lib/appManagers/appPollsManager"; import { toast } from "./toast"; const InputField = (placeholder: string, label: string, name: string) => { @@ -74,25 +74,21 @@ export default class PopupCreatePoll extends PopupElement { //const randomID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)]; //const randomIDS = bigint(randomID[0]).shiftLeft(32).add(bigint(randomID[1])).toString(); - const poll: Partial = {}; - poll._ = 'poll'; + const poll: Poll = { + _: 'poll', + question, + answers: answers.map((value, idx) => { + return { + _: 'pollAnswer', + text: value, + option: new Uint8Array([idx]) + }; + }), + id: undefined + }; //poll.id = randomIDS; - poll.flags = 0; - poll.question = question; - poll.answers = answers.map((value, idx) => { - return { - _: 'pollAnswer', - text: value, - option: new Uint8Array([idx]) - }; - }); - - appMessagesManager.sendOther($rootScope.selectedPeerID, { - _: 'inputMediaPoll', - flags: 0, - poll - }); + appMessagesManager.sendOther($rootScope.selectedPeerID, appPollsManager.getInputMediaPoll(poll)); }; onInput = (e: Event) => { diff --git a/src/components/sidebarLeft/tabs/editFolder.ts b/src/components/sidebarLeft/tabs/editFolder.ts index 46f5a327..6a5135c7 100644 --- a/src/components/sidebarLeft/tabs/editFolder.ts +++ b/src/components/sidebarLeft/tabs/editFolder.ts @@ -1,12 +1,12 @@ -import { SliderTab } from "../../slider"; import appSidebarLeft, { AppSidebarLeft } from ".."; -import lottieLoader, { RLottiePlayer } from "../../../lib/lottieLoader"; -import appMessagesManager, { MyDialogFilter as DialogFilter } from "../../../lib/appManagers/appMessagesManager"; -import { parseMenuButtonsTo } from "../../misc"; import appDialogsManager from "../../../lib/appManagers/appDialogsManager"; +import appMessagesManager, { MyDialogFilter as DialogFilter } from "../../../lib/appManagers/appMessagesManager"; +import lottieLoader, { RLottiePlayer } from "../../../lib/lottieLoader"; import { copy, deepEqual } from "../../../lib/utils"; -import { toast } from "../../toast"; +import { parseMenuButtonsTo } from "../../misc"; import { ripple } from "../../ripple"; +import { SliderTab } from "../../slider"; +import { toast } from "../../toast"; const MAX_FOLDER_NAME_LENGTH = 12; @@ -243,7 +243,6 @@ export default class AppEditFolderTab implements SliderTab { if(filter === undefined) { this.setFilter({ _: 'dialogFilter', - flags: 0, id: 0, title: '', pFlags: {}, diff --git a/src/index.hbs b/src/index.hbs index 240f79f3..2dba5788 100644 --- a/src/index.hbs +++ b/src/index.hbs @@ -494,14 +494,6 @@
-
- - - - - - -