2021-08-03 04:44:13 +03:00
/ *
* https : //github.com/morethanwords/tweb
* Copyright ( C ) 2019 - 2021 Eduard Kuzmenko
* https : //github.com/morethanwords/tweb/blob/master/LICENSE
* /
2022-06-17 20:01:43 +04:00
import type { MyDocument } from "../../lib/appManagers/appDocsManager" ;
2021-08-03 04:44:13 +03:00
import type { AppImManager } from '../../lib/appManagers/appImManager' ;
2022-06-17 20:01:43 +04:00
import type { MyDraftMessage } from '../../lib/appManagers/appDraftsManager' ;
2021-08-03 04:44:13 +03:00
import type Chat from './chat' ;
import Recorder from '../../../public/recorder.min' ;
2022-06-17 20:01:43 +04:00
import IS_TOUCH_SUPPORTED from "../../environment/touchSupport" ;
2021-08-03 04:44:13 +03:00
//import Recorder from '../opus-recorder/dist/recorder.min';
import opusDecodeController from "../../lib/opusDecodeController" ;
2021-10-25 20:10:22 +03:00
import ButtonMenu , { ButtonMenuItemOptions } from '../buttonMenu' ;
2021-08-03 04:44:13 +03:00
import emoticonsDropdown from "../emoticonsDropdown" ;
import PopupCreatePoll from "../popups/createPoll" ;
import PopupForward from '../popups/forward' ;
import PopupNewMedia from '../popups/newMedia' ;
import { toast } from "../toast" ;
import { wrapReply } from "../wrappers" ;
import InputField from '../inputField' ;
2022-03-04 14:55:08 +02:00
import { MessageEntity , DraftMessage , WebPage , Message , ChatFull , UserFull } from '../../layer' ;
2021-08-03 04:44:13 +03:00
import StickersHelper from './stickersHelper' ;
import ButtonIcon from '../buttonIcon' ;
import ButtonMenuToggle from '../buttonMenuToggle' ;
2021-09-23 20:11:33 +04:00
import ListenerSetter , { Listener } from '../../helpers/listenerSetter' ;
2021-08-03 04:44:13 +03:00
import Button from '../button' ;
import PopupSchedule from '../popups/schedule' ;
import SendMenu from './sendContextMenu' ;
import rootScope from '../../lib/rootScope' ;
import PopupPinMessage from '../popups/unpinMessage' ;
2022-06-17 20:01:43 +04:00
import tsNow from '../../helpers/tsNow' ;
2021-09-23 20:27:10 +04:00
import appNavigationController , { NavigationItem } from '../appNavigationController' ;
2021-09-26 17:59:10 +04:00
import { IS_MOBILE , IS_MOBILE_SAFARI } from '../../environment/userAgent' ;
2021-09-23 19:46:03 +04:00
import I18n , { i18n , join , LangPackKey } from '../../lib/langPack' ;
2021-08-03 04:44:13 +03:00
import { generateTail } from './bubbles' ;
import findUpClassName from '../../helpers/dom/findUpClassName' ;
import ButtonCorner from '../buttonCorner' ;
import blurActiveElement from '../../helpers/dom/blurActiveElement' ;
2022-04-15 15:09:21 +03:00
import cancelEvent from '../../helpers/dom/cancelEvent' ;
2021-08-03 04:44:13 +03:00
import cancelSelection from '../../helpers/dom/cancelSelection' ;
2021-09-23 20:11:33 +04:00
import { attachClickEvent , simulateClickEvent } from '../../helpers/dom/clickEvent' ;
2021-08-03 04:44:13 +03:00
import getRichValue from '../../helpers/dom/getRichValue' ;
import isInputEmpty from '../../helpers/dom/isInputEmpty' ;
import isSendShortcutPressed from '../../helpers/dom/isSendShortcutPressed' ;
import placeCaretAtEnd from '../../helpers/dom/placeCaretAtEnd' ;
import { MarkdownType , markdownTags } from '../../helpers/dom/getRichElementValue' ;
import getRichValueWithCaret from '../../helpers/dom/getRichValueWithCaret' ;
import EmojiHelper from './emojiHelper' ;
import CommandsHelper from './commandsHelper' ;
import AutocompleteHelperController from './autocompleteHelperController' ;
import AutocompleteHelper from './autocompleteHelper' ;
import MentionsHelper from './mentionsHelper' ;
import fixSafariStickyInput from '../../helpers/dom/fixSafariStickyInput' ;
import ReplyKeyboard from './replyKeyboard' ;
import InlineHelper from './inlineHelper' ;
import debounce from '../../helpers/schedulers/debounce' ;
import noop from '../../helpers/noop' ;
2022-06-17 20:01:43 +04:00
import { putPreloader } from '../putPreloader' ;
2021-08-03 04:44:13 +03:00
import SetTransition from '../singleTransition' ;
2021-08-09 17:34:08 +03:00
import PeerTitle from '../peerTitle' ;
2021-08-09 21:47:45 +03:00
import { fastRaf } from '../../helpers/schedulers' ;
import PopupDeleteMessages from '../popups/deleteMessages' ;
2021-08-24 19:33:04 +03:00
import fixSafariStickyInputFocusing , { IS_STICKY_INPUT_BUGGED } from '../../helpers/dom/fixSafariStickyInputFocusing' ;
2021-09-23 20:11:33 +04:00
import PopupPeer from '../popups/peer' ;
2021-09-26 17:59:10 +04:00
import MEDIA_MIME_TYPES_SUPPORTED from '../../environment/mediaMimeTypesSupport' ;
2021-10-07 18:48:18 +04:00
import appMediaPlaybackController from '../appMediaPlaybackController' ;
2022-01-15 02:01:50 +04:00
import { BOT_START_PARAM , NULL_PEER_ID } from '../../lib/mtproto/mtproto_config' ;
2021-11-11 02:58:04 +04:00
import setCaretAt from '../../helpers/dom/setCaretAt' ;
2021-11-29 17:51:29 +04:00
import CheckboxField from '../checkboxField' ;
import DropdownHover from '../../helpers/dropdownHover' ;
import RadioForm from '../radioForm' ;
import findUpTag from '../../helpers/dom/findUpTag' ;
2022-01-15 02:01:50 +04:00
import toggleDisability from '../../helpers/dom/toggleDisability' ;
2022-02-20 17:37:37 +02:00
import callbackify from '../../helpers/callbackify' ;
2022-03-04 14:55:08 +02:00
import ChatBotCommands from './botCommands' ;
2022-03-23 17:15:07 +02:00
import copy from '../../helpers/object/copy' ;
2022-03-26 03:55:57 +02:00
import toHHMMSS from '../../helpers/string/toHHMMSS' ;
2022-04-19 22:33:56 +03:00
import documentFragmentToHTML from '../../helpers/dom/documentFragmentToHTML' ;
2022-04-25 17:54:30 +03:00
import PopupElement from '../popups' ;
import getEmojiEntityFromEmoji from '../../lib/richTextProcessor/getEmojiEntityFromEmoji' ;
import mergeEntities from '../../lib/richTextProcessor/mergeEntities' ;
import parseEntities from '../../lib/richTextProcessor/parseEntities' ;
import parseMarkdown from '../../lib/richTextProcessor/parseMarkdown' ;
import wrapDraftText from '../../lib/richTextProcessor/wrapDraftText' ;
import wrapDraft from '../wrappers/draft' ;
import wrapMessageForReply from '../wrappers/messageForReply' ;
2022-06-17 20:01:43 +04:00
import getServerMessageId from '../../lib/appManagers/utils/messageId/getServerMessageId' ;
import { AppManagers } from '../../lib/appManagers/managers' ;
import contextMenuController from "../../helpers/contextMenuController" ;
import { emojiFromCodePoints } from "../../vendor/emoji" ;
import { modifyAckedPromise } from "../../helpers/modifyAckedResult" ;
import ChatSendAs from "./sendAs" ;
2022-06-18 04:02:20 +04:00
import filterAsync from "../../helpers/array/filterAsync" ;
2021-08-03 04:44:13 +03:00
const RECORD_MIN_TIME = 500 ;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.' ;
type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply' ;
export default class ChatInput {
// private static AUTO_COMPLETE_REG_EXP = /(\s|^)((?::|.)(?!.*[:@]).*|(?:[@\/]\S*))$/;
private static AUTO_COMPLETE_REG_EXP = /(\s|^)((?:(?:@|^\/)\S*)|(?::|^[^:@\/])(?!.*[:@\/]).*)$/ ;
public messageInput : HTMLElement ;
public messageInputField : InputField ;
private fileInput : HTMLInputElement ;
private inputMessageContainer : HTMLDivElement ;
private btnSend : HTMLButtonElement ;
private btnCancelRecord : HTMLButtonElement ;
private lastUrl = '' ;
private lastTimeType = 0 ;
public chatInput : HTMLElement ;
2022-01-12 22:17:34 +04:00
public inputContainer : HTMLElement ;
2021-08-03 04:44:13 +03:00
public rowsWrapper : HTMLDivElement ;
private newMessageWrapper : HTMLDivElement ;
private btnToggleEmoticons : HTMLButtonElement ;
private btnToggleReplyMarkup : HTMLButtonElement ;
private btnSendContainer : HTMLDivElement ;
private replyKeyboard : ReplyKeyboard ;
2022-02-20 17:37:37 +02:00
private attachMenu : HTMLElement ;
2022-06-17 20:01:43 +04:00
private attachMenuButtons : ( ButtonMenuItemOptions & { verify : ( peerId : PeerId , threadId : number ) = > boolean | Promise < boolean > } ) [ ] ;
2021-08-03 04:44:13 +03:00
private sendMenu : SendMenu ;
private replyElements : {
2021-08-09 17:34:08 +03:00
container : HTMLElement ,
2021-10-21 17:16:43 +04:00
cancelBtn : HTMLButtonElement ,
iconBtn : HTMLButtonElement
2021-08-09 17:34:08 +03:00
} = { } as any ;
2021-08-03 04:44:13 +03:00
2021-11-29 17:51:29 +04:00
private forwardElements : {
changePeer : ButtonMenuItemOptions ,
showSender : ButtonMenuItemOptions ,
hideSender : ButtonMenuItemOptions ,
showCaption : ButtonMenuItemOptions ,
hideCaption : ButtonMenuItemOptions ,
container : HTMLElement ,
modifyArgs? : ButtonMenuItemOptions [ ]
2021-12-02 17:44:18 +04:00
} ;
2021-11-29 17:51:29 +04:00
private forwardHover : DropdownHover ;
private forwardWasDroppingAuthor : boolean ;
2021-10-25 20:10:22 +03:00
2021-08-09 21:47:45 +03:00
private getWebPagePromise : Promise < void > ;
private willSendWebPage : WebPage = null ;
2021-10-21 18:15:31 +04:00
private forwarding : { [ fromPeerId : PeerId ] : number [ ] } ;
2021-08-03 04:44:13 +03:00
public replyToMsgId : number ;
public editMsgId : number ;
2021-10-21 17:16:43 +04:00
public editMessage : Message.message ;
2021-08-03 04:44:13 +03:00
private noWebPage : true ;
public scheduleDate : number ;
public sendSilent : true ;
2022-01-12 22:17:34 +04:00
public startParam : string ;
2021-08-03 04:44:13 +03:00
private recorder : any ;
public recording = false ;
private recordCanceled = false ;
private recordTimeEl : HTMLElement ;
private recordRippleEl : HTMLElement ;
private recordStartTime = 0 ;
2021-09-23 20:27:10 +04:00
private recordingOverlayListener : Listener ;
private recordingNavigationItem : NavigationItem ;
2021-08-03 04:44:13 +03:00
// private scrollTop = 0;
// private scrollOffsetTop = 0;
// private scrollDiff = 0;
public helperType : Exclude < ChatInputHelperType , 'webpage' > ;
2022-06-28 02:44:25 +02:00
private helperFunc : ( ) = > void | Promise < void > ;
2021-08-03 04:44:13 +03:00
private helperWaitingForward : boolean ;
public willAttachType : 'document' | 'media' ;
private lockRedo = false ;
private canRedoFromHTML = '' ;
private readonly undoHistory : string [ ] = [ ] ;
private readonly executedHistory : string [ ] = [ ] ;
private canUndoFromHTML = '' ;
private autocompleteHelperController : AutocompleteHelperController ;
private stickersHelper : StickersHelper ;
private emojiHelper : EmojiHelper ;
private commandsHelper : CommandsHelper ;
private mentionsHelper : MentionsHelper ;
private inlineHelper : InlineHelper ;
private listenerSetter : ListenerSetter ;
private pinnedControlBtn : HTMLButtonElement ;
private goDownBtn : HTMLButtonElement ;
private goDownUnreadBadge : HTMLElement ;
2021-09-01 20:17:06 +03:00
private goMentionBtn : HTMLButtonElement ;
private goMentionUnreadBadge : HTMLSpanElement ;
2021-08-03 04:44:13 +03:00
private btnScheduled : HTMLButtonElement ;
private btnPreloader : HTMLButtonElement ;
private saveDraftDebounced : ( ) = > void ;
private fakeRowsWrapper : HTMLDivElement ;
private previousQuery : string ;
2021-10-07 18:48:18 +04:00
private releaseMediaPlayback : ( ) = > void ;
2022-01-12 22:17:34 +04:00
private botStartBtn : HTMLButtonElement ;
private rowsWrapperWrapper : HTMLDivElement ;
2022-01-13 02:02:14 +04:00
private controlContainer : HTMLElement ;
private fakeSelectionWrapper : HTMLDivElement ;
2022-01-12 22:17:34 +04:00
private fakeWrapperTo : HTMLElement ;
2022-01-15 02:01:50 +04:00
private toggleBotStartBtnDisability : ( ) = > void ;
2022-01-12 22:17:34 +04:00
2022-03-04 14:55:08 +02:00
private botCommandsToggle : HTMLElement ;
private botCommands : ChatBotCommands ;
private botCommandsIcon : HTMLDivElement ;
2022-06-17 20:01:43 +04:00
private hasBotCommands : boolean ;
2022-03-04 14:55:08 +02:00
2022-01-12 22:17:34 +04:00
// private activeContainer: HTMLElement;
2021-08-03 04:44:13 +03:00
2022-06-17 20:01:43 +04:00
private sendAs : ChatSendAs ;
public sendAsPeerId : PeerId ;
2022-02-20 17:37:37 +02:00
constructor (
private chat : Chat ,
2021-08-03 04:44:13 +03:00
private appImManager : AppImManager ,
2022-06-17 20:01:43 +04:00
private managers : AppManagers
2021-08-03 04:44:13 +03:00
) {
this . listenerSetter = new ListenerSetter ( ) ;
}
public construct() {
this . chatInput = document . createElement ( 'div' ) ;
2022-06-17 20:01:43 +04:00
this . chatInput . classList . add ( 'chat-input' , 'hide' ) ;
2021-08-03 04:44:13 +03:00
this . inputContainer = document . createElement ( 'div' ) ;
this . inputContainer . classList . add ( 'chat-input-container' ) ;
2022-01-12 22:17:34 +04:00
this . rowsWrapperWrapper = document . createElement ( 'div' ) ;
this . rowsWrapperWrapper . classList . add ( 'rows-wrapper-wrapper' ) ;
2021-08-03 04:44:13 +03:00
this . rowsWrapper = document . createElement ( 'div' ) ;
this . rowsWrapper . classList . add ( 'rows-wrapper' , 'chat-input-wrapper' ) ;
2022-01-12 22:17:34 +04:00
this . rowsWrapperWrapper . append ( this . rowsWrapper ) ;
2021-08-03 04:44:13 +03:00
const tail = generateTail ( ) ;
this . rowsWrapper . append ( tail ) ;
const fakeRowsWrapper = this . fakeRowsWrapper = document . createElement ( 'div' ) ;
fakeRowsWrapper . classList . add ( 'fake-wrapper' , 'fake-rows-wrapper' ) ;
2022-01-12 22:17:34 +04:00
const fakeSelectionWrapper = this . fakeSelectionWrapper = document . createElement ( 'div' ) ;
2021-08-03 04:44:13 +03:00
fakeSelectionWrapper . classList . add ( 'fake-wrapper' , 'fake-selection-wrapper' ) ;
2022-01-12 22:17:34 +04:00
this . inputContainer . append ( this . rowsWrapperWrapper , fakeRowsWrapper , fakeSelectionWrapper ) ;
2021-08-03 04:44:13 +03:00
this . chatInput . append ( this . inputContainer ) ;
2021-09-01 20:17:06 +03:00
this . goDownBtn = ButtonCorner ( { icon : 'arrow_down' , className : 'bubbles-corner-button bubbles-go-down hide' } ) ;
2021-08-03 04:44:13 +03:00
this . inputContainer . append ( this . goDownBtn ) ;
attachClickEvent ( this . goDownBtn , ( e ) = > {
cancelEvent ( e ) ;
this . chat . bubbles . onGoDownClick ( ) ;
} , { listenerSetter : this.listenerSetter } ) ;
// * constructor end
/ * l e t s e t S c r o l l T o p T i m e o u t : n u m b e r ;
// @ts-ignore
let height = window . visualViewport . height ; * /
// @ts-ignore
// this.listenerSetter.add(window.visualViewport)('resize', () => {
// const scrollable = this.chat.bubbles.scrollable;
// const wasScrolledDown = scrollable.isScrolledDown;
// /* if(wasScrolledDown) {
// this.saveScroll();
// } */
// // @ts-ignore
// let newHeight = window.visualViewport.height;
// const diff = height - newHeight;
// const scrollTop = scrollable.scrollTop;
// const needScrollTop = wasScrolledDown ? scrollable.scrollHeight : scrollTop + diff; // * wasScrolledDown это проверка для десктоп хрома, когда пропадает панель загрузок снизу
// console.log('resize before', scrollable.scrollTop, scrollable.container.clientHeight, scrollable.scrollHeight, wasScrolledDown, scrollable.lastScrollTop, diff, needScrollTop);
// scrollable.scrollTop = needScrollTop;
// if(setScrollTopTimeout) clearTimeout(setScrollTopTimeout);
// setScrollTopTimeout = window.setTimeout(() => {
// const diff = height - newHeight;
// const isScrolledDown = scrollable.scrollHeight - Math.round(scrollable.scrollTop + scrollable.container.offsetHeight + diff) <= 1;
// height = newHeight;
// scrollable.scrollTop = needScrollTop;
// console.log('resize after', scrollable.scrollTop, scrollable.container.clientHeight, scrollable.scrollHeight, scrollable.isScrolledDown, scrollable.lastScrollTop, isScrolledDown);
// /* if(isScrolledDown) {
// scrollable.scrollTop = scrollable.scrollHeight;
// } */
// //scrollable.scrollTop += diff;
// setScrollTopTimeout = 0;
// }, 0);
// });
// ! Can't use it with resizeObserver
/ * t h i s . l i s t e n e r S e t t e r . a d d ( w i n d o w . v i s u a l V i e w p o r t ) ( ' r e s i z e ' , ( ) = > {
const scrollable = this . chat . bubbles . scrollable ;
const wasScrolledDown = scrollable . isScrolledDown ;
// @ts-ignore
let newHeight = window . visualViewport . height ;
const diff = height - newHeight ;
const needScrollTop = wasScrolledDown ? scrollable.scrollHeight : scrollable.scrollTop + diff ; // * wasScrolledDown это проверка для десктоп хрома, когда пропадает панель загрузок снизу
//console.log('resize before', scrollable.scrollTop, scrollable.container.clientHeight, scrollable.scrollHeight, wasScrolledDown, scrollable.lastScrollTop, diff, needScrollTop);
scrollable . scrollTop = needScrollTop ;
height = newHeight ;
if ( setScrollTopTimeout ) clearTimeout ( setScrollTopTimeout ) ;
setScrollTopTimeout = window . setTimeout ( ( ) = > { // * try again for scrolled down Android Chrome
scrollable . scrollTop = needScrollTop ;
//console.log('resize after', scrollable.scrollTop, scrollable.container.clientHeight, scrollable.scrollHeight, scrollable.isScrolledDown, scrollable.lastScrollTop, isScrolledDown);
setScrollTopTimeout = 0 ;
} , 0 ) ;
} ) ; * /
2022-01-13 02:02:14 +04:00
const c = this . controlContainer = document . createElement ( 'div' ) ;
c . classList . add ( 'chat-input-control' , 'chat-input-wrapper' ) ;
this . inputContainer . append ( c ) ;
2021-08-03 04:44:13 +03:00
}
public constructPeerHelpers() {
this . replyElements . container = document . createElement ( 'div' ) ;
this . replyElements . container . classList . add ( 'reply-wrapper' ) ;
2021-10-21 17:16:43 +04:00
this . replyElements . iconBtn = ButtonIcon ( '' ) ;
this . replyElements . cancelBtn = ButtonIcon ( 'close reply-cancel' , { noRipple : true } ) ;
2021-08-03 04:44:13 +03:00
2021-10-21 17:16:43 +04:00
this . replyElements . container . append ( this . replyElements . iconBtn , this . replyElements . cancelBtn ) ;
2021-08-03 04:44:13 +03:00
2021-11-29 17:51:29 +04:00
//
const onHideAuthorClick = ( ) = > {
isChangingAuthor = true ;
return this . canToggleHideAuthor ( ) ;
} ;
const onHideCaptionClick = ( ) = > {
isChangingAuthor = false ;
} ;
const forwardElements : ChatInput [ 'forwardElements' ] = this . forwardElements = { } as any ;
let isChangingAuthor = false ;
const forwardButtons : ButtonMenuItemOptions [ ] = [
forwardElements . showSender = {
text : 'Chat.Alert.Forward.Action.Show1' ,
onClick : onHideAuthorClick ,
checkboxField : new CheckboxField ( { checked : true } )
} ,
forwardElements . hideSender = {
text : 'Chat.Alert.Forward.Action.Hide1' ,
onClick : onHideAuthorClick ,
checkboxField : new CheckboxField ( { checked : false } )
} ,
forwardElements . showCaption = {
text : 'Chat.Alert.Forward.Action.ShowCaption' ,
onClick : onHideCaptionClick ,
checkboxField : new CheckboxField ( { checked : true } )
} ,
forwardElements . hideCaption = {
text : 'Chat.Alert.Forward.Action.HideCaption' ,
onClick : onHideCaptionClick ,
checkboxField : new CheckboxField ( { checked : false } )
} ,
forwardElements . changePeer = {
text : 'Chat.Alert.Forward.Action.Another' ,
onClick : ( ) = > {
this . changeForwardRecipient ( ) ;
} ,
icon : 'replace'
}
] ;
const forwardBtnMenu = forwardElements . container = ButtonMenu ( forwardButtons , this . listenerSetter ) ;
// forwardBtnMenu.classList.add('top-center');
const children = Array . from ( forwardBtnMenu . children ) as HTMLElement [ ] ;
const groups : {
elements : HTMLElement [ ] ,
onChange : ( value : string , event : Event ) = > void
} [ ] = [ {
elements : children.slice ( 0 , 2 ) ,
onChange : ( value , e ) = > {
const checked = ! ! + value ;
if ( isChangingAuthor ) {
this . forwardWasDroppingAuthor = ! checked ;
}
const replyTitle = this . replyElements . container . querySelector ( '.reply-title' ) ;
if ( replyTitle ) {
const el = replyTitle . firstElementChild as HTMLElement ;
const i = I18n . weakMap . get ( el ) as I18n . IntlElement ;
const langPackKey : LangPackKey = forwardElements . showSender . checkboxField . checked ? 'Chat.Accessory.Forward' : 'Chat.Accessory.Hidden' ;
i . key = langPackKey ;
i . update ( ) ;
}
}
} , {
elements : children.slice ( 2 , 4 ) ,
onChange : ( value ) = > {
const checked = ! ! + value ;
let b : ButtonMenuItemOptions ;
if ( checked && this . forwardWasDroppingAuthor !== undefined ) {
b = this . forwardWasDroppingAuthor ? forwardElements.hideSender : forwardElements.showSender ;
} else {
b = checked ? forwardElements.showSender : forwardElements.hideSender ;
}
b . checkboxField . checked = true ;
}
} ] ;
2022-06-17 20:01:43 +04:00
groups . forEach ( ( group ) = > {
const container = RadioForm ( group . elements . map ( ( e ) = > {
2021-11-29 17:51:29 +04:00
return {
container : e ,
input : e.querySelector ( 'input' )
} ;
} ) , group . onChange ) ;
const hr = document . createElement ( 'hr' ) ;
container . append ( hr ) ;
forwardBtnMenu . append ( container ) ;
} ) ;
2021-10-25 20:10:22 +03:00
2021-11-29 17:51:29 +04:00
forwardBtnMenu . append ( forwardElements . changePeer . element ) ;
2021-10-25 20:10:22 +03:00
2021-11-29 17:51:29 +04:00
if ( ! IS_TOUCH_SUPPORTED ) {
const forwardHover = this . forwardHover = new DropdownHover ( {
element : forwardBtnMenu
} ) ;
}
forwardElements . modifyArgs = forwardButtons . slice ( 0 , - 1 ) ;
2021-10-25 20:10:22 +03:00
this . replyElements . container . append ( forwardBtnMenu ) ;
2021-11-29 17:51:29 +04:00
forwardElements . modifyArgs . forEach ( ( b , idx ) = > {
const { input } = b . checkboxField ;
input . type = 'radio' ;
input . name = idx < 2 ? 'author' : 'caption' ;
input . value = '' + + ! ( idx % 2 ) ;
} ) ;
//
2021-08-03 04:44:13 +03:00
this . newMessageWrapper = document . createElement ( 'div' ) ;
this . newMessageWrapper . classList . add ( 'new-message-wrapper' ) ;
this . btnToggleEmoticons = ButtonIcon ( 'none toggle-emoticons' , { noRipple : true } ) ;
this . inputMessageContainer = document . createElement ( 'div' ) ;
this . inputMessageContainer . classList . add ( 'input-message-container' ) ;
if ( this . chat . type === 'chat' ) {
this . goDownUnreadBadge = document . createElement ( 'span' ) ;
this . goDownUnreadBadge . classList . add ( 'badge' , 'badge-24' , 'badge-primary' ) ;
this . goDownBtn . append ( this . goDownUnreadBadge ) ;
2021-09-01 20:17:06 +03:00
this . goMentionBtn = ButtonCorner ( { icon : 'mention' , className : 'bubbles-corner-button bubbles-go-mention' } ) ;
this . goMentionUnreadBadge = document . createElement ( 'span' ) ;
this . goMentionUnreadBadge . classList . add ( 'badge' , 'badge-24' , 'badge-primary' ) ;
this . goMentionBtn . append ( this . goMentionUnreadBadge ) ;
this . inputContainer . append ( this . goMentionBtn ) ;
attachClickEvent ( this . goMentionBtn , ( e ) = > {
cancelEvent ( e ) ;
2022-06-24 21:23:12 +04:00
const middleware = this . chat . bubbles . getMiddleware ( ) ;
this . managers . appMessagesManager . goToNextMention ( this . chat . peerId ) . then ( ( mid ) = > {
if ( ! middleware ( ) ) {
return ;
}
if ( mid ) {
this . chat . setMessageId ( mid ) ;
}
} ) ;
2021-09-01 20:17:06 +03:00
} , { listenerSetter : this.listenerSetter } ) ;
2021-08-03 04:44:13 +03:00
this . btnScheduled = ButtonIcon ( 'scheduled btn-scheduled float hide' , { noRipple : true } ) ;
attachClickEvent ( this . btnScheduled , ( e ) = > {
this . appImManager . openScheduled ( this . chat . peerId ) ;
} , { listenerSetter : this.listenerSetter } ) ;
2021-09-26 17:59:10 +04:00
this . listenerSetter . add ( rootScope ) ( 'scheduled_new' , ( { peerId } ) = > {
2021-08-03 04:44:13 +03:00
if ( this . chat . peerId !== peerId ) {
return ;
}
this . btnScheduled . classList . remove ( 'hide' ) ;
} ) ;
2021-09-26 17:59:10 +04:00
this . listenerSetter . add ( rootScope ) ( 'scheduled_delete' , ( { peerId } ) = > {
2021-08-03 04:44:13 +03:00
if ( this . chat . peerId !== peerId ) {
return ;
}
2022-06-17 20:01:43 +04:00
this . managers . appMessagesManager . getScheduledMessages ( this . chat . peerId ) . then ( ( value ) = > {
2021-08-03 04:44:13 +03:00
this . btnScheduled . classList . toggle ( 'hide' , ! value . length ) ;
} ) ;
} ) ;
this . btnToggleReplyMarkup = ButtonIcon ( 'botcom toggle-reply-markup float hide' , { noRipple : true } ) ;
this . replyKeyboard = new ReplyKeyboard ( {
appendTo : this.rowsWrapper ,
listenerSetter : this.listenerSetter ,
2022-06-17 20:01:43 +04:00
managers : this.managers ,
2021-09-15 10:47:55 +04:00
btnHover : this.btnToggleReplyMarkup ,
chatInput : this
2021-08-03 04:44:13 +03:00
} ) ;
this . listenerSetter . add ( this . replyKeyboard ) ( 'open' , ( ) = > this . btnToggleReplyMarkup . classList . add ( 'active' ) ) ;
this . listenerSetter . add ( this . replyKeyboard ) ( 'close' , ( ) = > this . btnToggleReplyMarkup . classList . remove ( 'active' ) ) ;
2022-03-04 14:55:08 +02:00
2022-06-17 20:01:43 +04:00
this . botCommands = new ChatBotCommands ( this . rowsWrapper , this , this . managers ) ;
2022-03-04 14:55:08 +02:00
this . botCommandsToggle = document . createElement ( 'div' ) ;
this . botCommandsToggle . classList . add ( 'new-message-bot-commands' ) ;
const scaler = document . createElement ( 'div' ) ;
scaler . classList . add ( 'new-message-bot-commands-icon-scale' ) ;
const icon = this . botCommandsIcon = document . createElement ( 'div' ) ;
icon . classList . add ( 'animated-menu-icon' , 'animated-menu-close-icon' ) ;
scaler . append ( icon ) ;
this . botCommandsToggle . append ( scaler ) ;
attachClickEvent ( this . botCommandsToggle , ( e ) = > {
cancelEvent ( e ) ;
const isShown = icon . classList . contains ( 'state-back' ) ;
if ( isShown ) {
this . botCommands . toggle ( true ) ;
icon . classList . remove ( 'state-back' ) ;
} else {
this . botCommands . setUserId ( this . chat . peerId . toUserId ( ) , this . chat . bubbles . getMiddleware ( ) ) ;
icon . classList . add ( 'state-back' ) ;
}
} , { listenerSetter : this.listenerSetter } ) ;
this . botCommands . addEventListener ( 'visible' , ( ) = > {
icon . classList . add ( 'state-back' ) ;
} ) ;
this . botCommands . addEventListener ( 'hiding' , ( ) = > {
icon . classList . remove ( 'state-back' ) ;
} ) ;
2021-08-03 04:44:13 +03:00
}
this . attachMenuButtons = [ {
icon : 'image' ,
text : 'Chat.Input.Attach.PhotoOrVideo' ,
onClick : ( ) = > {
this . fileInput . value = '' ;
2021-09-26 17:59:10 +04:00
const accept = [ . . . MEDIA_MIME_TYPES_SUPPORTED ] . join ( ', ' ) ;
this . fileInput . setAttribute ( 'accept' , accept ) ;
2021-08-03 04:44:13 +03:00
this . willAttachType = 'media' ;
this . fileInput . click ( ) ;
} ,
2022-01-12 22:17:34 +04:00
verify : ( ) = > this . chat . canSend ( 'send_media' )
2021-08-03 04:44:13 +03:00
} , {
icon : 'document' ,
text : 'Chat.Input.Attach.Document' ,
onClick : ( ) = > {
this . fileInput . value = '' ;
this . fileInput . removeAttribute ( 'accept' ) ;
this . willAttachType = 'document' ;
this . fileInput . click ( ) ;
} ,
2022-01-12 22:17:34 +04:00
verify : ( ) = > this . chat . canSend ( 'send_media' )
2021-08-03 04:44:13 +03:00
} , {
icon : 'poll' ,
text : 'Poll' ,
onClick : ( ) = > {
2022-04-25 17:54:30 +03:00
PopupElement . createPopup ( PopupCreatePoll , this . chat ) . show ( ) ;
2021-08-03 04:44:13 +03:00
} ,
2022-01-12 22:17:34 +04:00
verify : ( peerId ) = > peerId . isAnyChat ( ) && this . chat . canSend ( 'send_polls' )
2021-08-03 04:44:13 +03:00
} ] ;
this . attachMenu = ButtonMenuToggle ( { noRipple : true , listenerSetter : this.listenerSetter } , 'top-left' , this . attachMenuButtons ) ;
this . attachMenu . classList . add ( 'attach-file' , 'tgico-attach' ) ;
this . attachMenu . classList . remove ( 'tgico-more' ) ;
//this.inputContainer.append(this.sendMenu);
this . recordTimeEl = document . createElement ( 'div' ) ;
this . recordTimeEl . classList . add ( 'record-time' ) ;
this . fileInput = document . createElement ( 'input' ) ;
this . fileInput . type = 'file' ;
this . fileInput . multiple = true ;
this . fileInput . style . display = 'none' ;
2022-06-17 20:01:43 +04:00
this . newMessageWrapper . append ( . . . [ this . botCommandsToggle , this . btnToggleEmoticons , this . inputMessageContainer , this . btnScheduled , this . btnToggleReplyMarkup , this . attachMenu , this . recordTimeEl , this . fileInput ] . filter ( Boolean ) ) ;
2021-08-03 04:44:13 +03:00
this . rowsWrapper . append ( this . replyElements . container ) ;
this . autocompleteHelperController = new AutocompleteHelperController ( ) ;
2022-06-17 20:01:43 +04:00
this . stickersHelper = new StickersHelper ( this . rowsWrapper , this . autocompleteHelperController , this . managers ) ;
this . emojiHelper = new EmojiHelper ( this . rowsWrapper , this . autocompleteHelperController , this , this . managers ) ;
this . commandsHelper = new CommandsHelper ( this . rowsWrapper , this . autocompleteHelperController , this , this . managers ) ;
this . mentionsHelper = new MentionsHelper ( this . rowsWrapper , this . autocompleteHelperController , this , this . managers ) ;
this . inlineHelper = new InlineHelper ( this . rowsWrapper , this . autocompleteHelperController , this . chat , this . managers ) ;
2021-08-03 04:44:13 +03:00
this . rowsWrapper . append ( this . newMessageWrapper ) ;
2022-03-24 16:41:17 +02:00
this . btnCancelRecord = ButtonIcon ( 'delete btn-circle z-depth-1 btn-record-cancel' ) ;
2021-08-03 04:44:13 +03:00
this . btnSendContainer = document . createElement ( 'div' ) ;
this . btnSendContainer . classList . add ( 'btn-send-container' ) ;
this . recordRippleEl = document . createElement ( 'div' ) ;
this . recordRippleEl . classList . add ( 'record-ripple' ) ;
this . btnSend = ButtonIcon ( 'none btn-circle z-depth-1 btn-send animated-button-icon' ) ;
this . btnSend . insertAdjacentHTML ( 'afterbegin' , `
< span class = "tgico tgico-send" > < / span >
< span class = "tgico tgico-schedule" > < / span >
< span class = "tgico tgico-check" > < / span >
2022-04-13 01:51:20 +03:00
< span class = "tgico tgico-microphone_filled" > < / span >
2021-08-03 04:44:13 +03:00
` );
this . btnSendContainer . append ( this . recordRippleEl , this . btnSend ) ;
if ( this . chat . type !== 'scheduled' ) {
this . sendMenu = new SendMenu ( {
onSilentClick : ( ) = > {
this . sendSilent = true ;
this . sendMessage ( ) ;
} ,
onScheduleClick : ( ) = > {
this . scheduleSending ( undefined ) ;
} ,
listenerSetter : this.listenerSetter ,
openSide : 'top-left' ,
onContextElement : this.btnSend ,
onOpen : ( ) = > {
2021-11-29 17:51:29 +04:00
return ! this . isInputEmpty ( ) || ! ! Object . keys ( this . forwarding ) . length ;
2021-08-03 04:44:13 +03:00
}
} ) ;
this . btnSendContainer . append ( this . sendMenu . sendMenu ) ;
}
this . inputContainer . append ( this . btnCancelRecord , this . btnSendContainer ) ;
emoticonsDropdown . attachButtonListener ( this . btnToggleEmoticons , this . listenerSetter ) ;
this . listenerSetter . add ( emoticonsDropdown ) ( 'open' , this . onEmoticonsOpen ) ;
this . listenerSetter . add ( emoticonsDropdown ) ( 'close' , this . onEmoticonsClose ) ;
this . attachMessageInputField ( ) ;
/ * t h i s . a t t a c h M e n u . a d d E v e n t L i s t e n e r ( ' m o u s e d o w n ' , ( e ) = > {
const hidden = this . attachMenu . querySelectorAll ( '.hide' ) ;
if ( hidden . length === this . attachMenuButtons . length ) {
toast ( POSTING_MEDIA_NOT_ALLOWED ) ;
cancelEvent ( e ) ;
return false ;
}
} , { passive : false , capture : true } ) ; * /
this . listenerSetter . add ( rootScope ) ( 'settings_updated' , ( ) = > {
if ( this . stickersHelper || this . emojiHelper ) {
// this.previousQuery = undefined;
this . previousQuery = '' ;
this . checkAutocomplete ( ) ;
/ * i f ( ! r o o t S c o p e . s e t t i n g s . s t i c k e r s . s u g g e s t ) {
this . stickersHelper . checkEmoticon ( '' ) ;
} else {
this . onMessageInput ( ) ;
} * /
}
if ( this . messageInputField ) {
this . messageInputField . onFakeInput ( ) ;
}
} ) ;
2021-09-26 17:59:10 +04:00
this . listenerSetter . add ( rootScope ) ( 'draft_updated' , ( { peerId , threadId , draft , force } ) = > {
2021-08-03 04:44:13 +03:00
if ( this . chat . threadId !== threadId || this . chat . peerId !== peerId ) return ;
this . setDraft ( draft , true , force ) ;
} ) ;
2022-06-17 20:01:43 +04:00
this . listenerSetter . add ( this . appImManager ) ( 'peer_changing' , ( chat ) = > {
2021-08-03 04:44:13 +03:00
if ( this . chat === chat ) {
this . saveDraft ( ) ;
}
} ) ;
2022-06-17 20:01:43 +04:00
this . listenerSetter . add ( this . appImManager ) ( 'chat_changing' , ( { from , to } ) = > {
2022-01-08 16:52:14 +04:00
if ( this . chat === from ) {
this . autocompleteHelperController . toggleListNavigation ( false ) ;
} else if ( this . chat === to ) {
this . autocompleteHelperController . toggleListNavigation ( true ) ;
}
} ) ;
2021-08-09 21:47:45 +03:00
if ( this . chat . type === 'scheduled' ) {
this . listenerSetter . add ( rootScope ) ( 'scheduled_delete' , ( { peerId , mids } ) = > {
if ( this . chat . peerId === peerId && mids . includes ( this . editMsgId ) ) {
this . onMessageSent ( ) ;
}
} ) ;
} else {
this . listenerSetter . add ( rootScope ) ( 'history_delete' , ( { peerId , msgs } ) = > {
2021-08-12 20:51:10 +03:00
if ( this . chat . peerId === peerId ) {
2021-08-24 19:33:04 +03:00
if ( msgs . has ( this . editMsgId ) ) {
2021-08-12 20:51:10 +03:00
this . onMessageSent ( ) ;
}
2021-08-24 19:33:04 +03:00
if ( this . replyToMsgId && msgs . has ( this . replyToMsgId ) ) {
2021-08-12 20:51:10 +03:00
this . clearHelper ( 'reply' ) ;
}
2022-01-15 02:01:50 +04:00
/ * i f ( t h i s . c h a t . i s S t a r t B u t t o n N e e d e d ( ) ) {
this . setStartParam ( BOT_START_PARAM ) ;
} * /
}
} ) ;
this . listenerSetter . add ( rootScope ) ( 'dialogs_multiupdate' , ( dialogs ) = > {
if ( dialogs [ this . chat . peerId ] ) {
if ( this . startParam === BOT_START_PARAM ) {
this . setStartParam ( ) ;
} else { // updateNewMessage comes earlier than dialog appers
this . center ( true ) ;
}
2021-08-09 21:47:45 +03:00
}
} ) ;
}
2021-08-03 04:44:13 +03:00
try {
this . recorder = new Recorder ( {
//encoderBitRate: 32,
//encoderPath: "../dist/encoderWorker.min.js",
encoderSampleRate : 48000 ,
monitorGain : 0 ,
numberOfChannels : 1 ,
recordingGain : 1 ,
reuseWorker : true
} ) ;
} catch ( err ) {
console . error ( 'Recorder constructor error:' , err ) ;
}
this . updateSendBtn ( ) ;
this . listenerSetter . add ( this . fileInput ) ( 'change' , ( e ) = > {
let files = ( e . target as HTMLInputElement & EventTarget ) . files ;
if ( ! files . length ) {
return ;
}
2022-04-25 17:54:30 +03:00
PopupElement . createPopup ( PopupNewMedia , this . chat , Array . from ( files ) . slice ( ) , this . willAttachType ) ;
2021-08-03 04:44:13 +03:00
this . fileInput . value = '' ;
} , false ) ;
/ * l e t t i m e = D a t e . n o w ( ) ;
this . btnSend . addEventListener ( 'touchstart' , ( e ) = > {
time = Date . now ( ) ;
} ) ;
let eventName1 = 'touchend' ;
this . btnSend . addEventListener ( eventName1 , ( e : Event ) = > {
//cancelEvent(e);
console . log ( eventName1 + ', time: ' + ( Date . now ( ) - time ) ) ;
} ) ;
let eventName = 'mousedown' ;
this . btnSend . addEventListener ( eventName , ( e : Event ) = > {
cancelEvent ( e ) ;
console . log ( eventName + ', time: ' + ( Date . now ( ) - time ) ) ;
} ) ; * /
attachClickEvent ( this . btnSend , this . onBtnSendClick , { listenerSetter : this.listenerSetter , touchMouseDown : true } ) ;
if ( this . recorder ) {
attachClickEvent ( this . btnCancelRecord , this . onCancelRecordClick , { listenerSetter : this.listenerSetter } ) ;
this . recorder . onstop = ( ) = > {
2022-01-13 02:02:14 +04:00
this . setRecording ( false ) ;
this . chatInput . classList . remove ( 'is-locked' ) ;
2021-08-03 04:44:13 +03:00
this . recordRippleEl . style . transform = '' ;
} ;
this . recorder . ondataavailable = ( typedArray : Uint8Array ) = > {
2021-10-07 18:48:18 +04:00
if ( this . releaseMediaPlayback ) {
this . releaseMediaPlayback ( ) ;
this . releaseMediaPlayback = undefined ;
}
2021-09-23 20:11:33 +04:00
if ( this . recordingOverlayListener ) {
this . listenerSetter . remove ( this . recordingOverlayListener ) ;
this . recordingOverlayListener = undefined ;
}
2021-09-23 20:27:10 +04:00
if ( this . recordingNavigationItem ) {
appNavigationController . removeItem ( this . recordingNavigationItem ) ;
this . recordingNavigationItem = undefined ;
}
2021-09-23 20:11:33 +04:00
if ( this . recordCanceled ) {
return ;
}
2021-09-23 17:41:02 +04:00
const { peerId , threadId } = this . chat ;
const replyToMsgId = this . replyToMsgId ;
2021-08-03 04:44:13 +03:00
const duration = ( Date . now ( ) - this . recordStartTime ) / 1000 | 0 ;
const dataBlob = new Blob ( [ typedArray ] , { type : 'audio/ogg' } ) ;
/ * c o n s t f i l e N a m e = n e w D a t e ( ) . t o I S O S t r i n g ( ) + " . o p u s " ;
console . log ( 'Recorder data received' , typedArray , dataBlob ) ; * /
//let perf = performance.now();
2022-06-17 20:01:43 +04:00
opusDecodeController . decode ( typedArray , true ) . then ( ( result ) = > {
2021-08-03 04:44:13 +03:00
//console.log('WAVEFORM!:', /* waveform, */performance.now() - perf);
opusDecodeController . setKeepAlive ( false ) ;
// тут objectURL ставится уже с audio/wav
2022-06-17 20:01:43 +04:00
this . managers . appMessagesManager . sendFile ( peerId , dataBlob , {
2021-08-03 04:44:13 +03:00
isVoiceMessage : true ,
isMedia : true ,
duration ,
waveform : result.waveform ,
objectURL : result.url ,
2021-09-23 17:41:02 +04:00
replyToMsgId ,
threadId ,
2021-08-03 04:44:13 +03:00
clearDraft : true
} ) ;
this . onMessageSent ( false , true ) ;
} ) ;
} ;
}
attachClickEvent ( this . replyElements . cancelBtn , this . onHelperCancel , { listenerSetter : this.listenerSetter } ) ;
attachClickEvent ( this . replyElements . container , this . onHelperClick , { listenerSetter : this.listenerSetter } ) ;
this . saveDraftDebounced = debounce ( ( ) = > this . saveDraft ( ) , 2500 , false , true ) ;
2022-01-08 16:52:14 +04:00
2022-01-12 22:17:34 +04:00
this . botStartBtn = Button ( 'btn-primary btn-transparent text-bold chat-input-control-button' ) ;
this . botStartBtn . append ( i18n ( 'BotStart' ) ) ;
2022-01-08 16:52:14 +04:00
2022-01-15 02:01:50 +04:00
attachClickEvent ( this . botStartBtn , ( ) = > {
const { startParam } = this ;
if ( startParam === undefined ) {
return ;
}
const toggle = this . toggleBotStartBtnDisability = toggleDisability ( [ this . botStartBtn ] , true ) ;
const peerId = this . chat . peerId ;
const middleware = this . chat . bubbles . getMiddleware ( ( ) = > {
return this . chat . peerId === peerId && this . startParam === startParam && this . toggleBotStartBtnDisability === toggle ;
} ) ;
2022-06-17 20:01:43 +04:00
this . managers . appMessagesManager . startBot ( peerId . toUserId ( ) , undefined , startParam ) . then ( ( ) = > {
2022-01-15 02:01:50 +04:00
if ( middleware ( ) ) {
toggle ( ) ;
this . toggleBotStartBtnDisability = undefined ;
this . setStartParam ( ) ;
}
} ) ;
} , { listenerSetter : this.listenerSetter } ) ;
2022-01-13 02:02:14 +04:00
this . controlContainer . append ( this . botStartBtn ) ;
2021-08-03 04:44:13 +03:00
}
public constructPinnedHelpers() {
2022-01-13 02:02:14 +04:00
this . pinnedControlBtn = Button ( 'btn-primary btn-transparent text-bold chat-input-control-button' , { icon : 'unpin' } ) ;
this . controlContainer . append ( this . pinnedControlBtn ) ;
2021-08-03 04:44:13 +03:00
this . listenerSetter . add ( this . pinnedControlBtn ) ( 'click' , ( ) = > {
const peerId = this . chat . peerId ;
new PopupPinMessage ( peerId , 0 , true , ( ) = > {
2022-01-13 03:54:33 +04:00
this . chat . appImManager . setPeer ( ) ; // * close tab
2021-08-03 04:44:13 +03:00
// ! костыль, это скроет закреплённые сообщения сразу, вместо того, чтобы ждать пока анимация перехода закончится
const originalChat = this . chat . appImManager . chat ;
if ( originalChat . topbar . pinnedMessage ) {
originalChat . topbar . pinnedMessage . pinnedMessageContainer . toggle ( true ) ;
}
} ) ;
} ) ;
this . chatInput . classList . add ( 'type-pinned' ) ;
}
2022-06-17 20:01:43 +04:00
public _center ( neededFakeContainer : HTMLElement , animate? : boolean ) {
2022-01-12 22:17:34 +04:00
if ( ! neededFakeContainer && ! this . inputContainer . classList . contains ( 'is-centering' ) ) {
return ;
}
2022-01-15 02:01:50 +04:00
if ( neededFakeContainer === this . fakeWrapperTo ) {
return ;
}
2022-01-12 22:17:34 +04:00
/ * i f ( n e e d e d F a k e C o n t a i n e r = = = t h i s . b o t S t a r t C o n t a i n e r & & t h i s . f a k e W r a p p e r T o = = = t h i s . f a k e S e l e c t i o n W r a p p e r ) {
this . inputContainer . classList . remove ( 'is-centering' ) ;
void this . rowsWrapper . offsetLeft ; // reflow
// this.inputContainer.classList.add('is-centering');
// void this.rowsWrapper.offsetLeft; // reflow
} * /
const fakeSelectionWrapper = neededFakeContainer || this . fakeWrapperTo ;
const forwards = ! ! neededFakeContainer ;
const oldFakeWrapperTo = this . fakeWrapperTo ;
let transform = '' , borderRadius = '' , needTranslateX : number ;
// if(forwards) {]
const fakeSelectionRect = fakeSelectionWrapper . getBoundingClientRect ( ) ;
const fakeRowsRect = this . fakeRowsWrapper . getBoundingClientRect ( ) ;
const widthFrom = fakeRowsRect . width ;
const widthTo = fakeSelectionRect . width ;
if ( widthFrom !== widthTo ) {
const scale = ( widthTo /* - 8 */ ) / widthFrom ;
const initTranslateX = ( widthFrom - widthTo ) / 2 ;
needTranslateX = fakeSelectionRect . left - fakeRowsRect . left - initTranslateX ;
if ( forwards ) {
transform = ` translateX( ${ needTranslateX } px) scaleX( ${ scale } ) ` ;
// transform = `translateX(0px) scaleX(${scale})`;
if ( scale < 1 ) {
const br = 12 ;
borderRadius = '' + ( br + br * ( 1 - scale ) ) + 'px' ;
}
}
//scale = widthTo / widthFrom;
}
// }
this . fakeWrapperTo = neededFakeContainer ;
2022-01-13 02:02:14 +04:00
const duration = animate ? 200 : 0 ;
2022-01-12 22:17:34 +04:00
SetTransition ( this . inputContainer , 'is-centering' , forwards , duration ) ;
SetTransition ( this . rowsWrapperWrapper , 'is-centering-to-control' , ! ! ( forwards && neededFakeContainer && neededFakeContainer . classList . contains ( 'chat-input-control' ) ) , duration ) ;
this . rowsWrapper . style . transform = transform ;
this . rowsWrapper . style . borderRadius = borderRadius ;
return {
transform ,
borderRadius ,
needTranslateX : oldFakeWrapperTo && (
(
neededFakeContainer &&
neededFakeContainer . classList . contains ( 'chat-input-control' ) &&
oldFakeWrapperTo === this . fakeSelectionWrapper
) || oldFakeWrapperTo . classList . contains ( 'chat-input-control' )
) ? needTranslateX * - . 5 : needTranslateX ,
widthFrom ,
widthTo
} ;
}
2022-06-17 20:01:43 +04:00
public async center ( animate = false ) {
return this . _center ( await this . getNeededFakeContainer ( ) , animate ) ;
}
2022-01-15 02:01:50 +04:00
public setStartParam ( startParam? : string ) {
if ( this . startParam === startParam ) {
return ;
}
this . startParam = startParam ;
this . center ( true ) ;
}
2022-06-28 19:49:52 +02:00
public async getNeededFakeContainer ( startParam = this . startParam ) {
2022-01-12 22:17:34 +04:00
if ( this . chat . selection . isSelecting ) {
return this . fakeSelectionWrapper ;
2022-06-17 20:01:43 +04:00
} else if (
2022-06-28 19:49:52 +02:00
startParam !== undefined ||
2022-06-17 20:01:43 +04:00
! ( await this . chat . canSend ( ) ) ||
2022-01-15 02:01:50 +04:00
this . chat . type === 'pinned' ||
2022-06-17 20:01:43 +04:00
await this . chat . isStartButtonNeeded ( )
2022-01-15 02:01:50 +04:00
) {
2022-01-13 02:02:14 +04:00
return this . controlContainer ;
2022-01-12 22:17:34 +04:00
}
}
// public getActiveContainer() {
// if(this.chat.selection.isSelecting) {
// return this.chat
// }
// return this.startParam !== undefined ? this.botStartContainer : this.rowsWrapper;
// }
// public setActiveContainer() {
// const container = this.activeContainer;
// const newContainer = this.getActiveContainer();
// if(newContainer === container) {
// return;
// }
// }
2021-08-03 04:44:13 +03:00
private onCancelRecordClick = ( e? : Event ) = > {
if ( e ) {
cancelEvent ( e ) ;
}
this . recordCanceled = true ;
this . recorder . stop ( ) ;
opusDecodeController . setKeepAlive ( false ) ;
} ;
private onEmoticonsOpen = ( ) = > {
2021-09-26 17:59:10 +04:00
const toggleClass = IS_TOUCH_SUPPORTED ? 'flip-icon' : 'active' ;
2021-08-03 04:44:13 +03:00
this . btnToggleEmoticons . classList . toggle ( toggleClass , true ) ;
} ;
private onEmoticonsClose = ( ) = > {
2021-09-26 17:59:10 +04:00
const toggleClass = IS_TOUCH_SUPPORTED ? 'flip-icon' : 'active' ;
2021-08-03 04:44:13 +03:00
this . btnToggleEmoticons . classList . toggle ( toggleClass , false ) ;
} ;
public getReadyToSend ( callback : ( ) = > void ) {
return this . chat . type === 'scheduled' ? ( this . scheduleSending ( callback ) , true ) : ( callback ( ) , false ) ;
}
2022-06-17 20:01:43 +04:00
public scheduleSending = async ( callback : ( ) = > void = this . sendMessage . bind ( this , true ) , initDate = new Date ( ) ) = > {
2021-11-29 17:51:29 +04:00
const { peerId } = this . chat ;
const middleware = this . chat . bubbles . getMiddleware ( ) ;
2022-06-17 20:01:43 +04:00
const canSendWhenOnline = rootScope . myId !== peerId && peerId . isUser ( ) && await this . managers . appUsersManager . isUserOnlineVisible ( peerId ) ;
2021-08-20 17:12:35 +03:00
2021-08-03 04:44:13 +03:00
new PopupSchedule ( initDate , ( timestamp ) = > {
2021-11-29 17:51:29 +04:00
if ( ! middleware ( ) ) {
return ;
}
2021-08-03 04:44:13 +03:00
const minTimestamp = ( Date . now ( ) / 1000 | 0 ) + 10 ;
if ( timestamp <= minTimestamp ) {
timestamp = undefined ;
}
this . scheduleDate = timestamp ;
callback ( ) ;
if ( this . chat . type !== 'scheduled' && timestamp ) {
2021-11-29 17:51:29 +04:00
setTimeout ( ( ) = > { // ! need timeout here because .forwardMessages will be called after timeout
if ( ! middleware ( ) ) {
return ;
}
this . appImManager . openScheduled ( peerId ) ;
} , 0 ) ;
2021-08-03 04:44:13 +03:00
}
2021-08-20 17:12:35 +03:00
} , canSendWhenOnline ) . show ( ) ;
2021-08-03 04:44:13 +03:00
} ;
2022-06-17 20:01:43 +04:00
public async setUnreadCount() {
2022-01-21 20:07:09 +04:00
if ( ! this . goDownUnreadBadge ) {
return ;
}
2022-06-17 20:01:43 +04:00
const dialog = await this . managers . appMessagesManager . getDialogOnly ( this . chat . peerId ) ;
2021-08-03 04:44:13 +03:00
const count = dialog ? . unread_count ;
this . goDownUnreadBadge . innerText = '' + ( count || '' ) ;
2022-06-17 20:01:43 +04:00
this . goDownUnreadBadge . classList . toggle ( 'badge-gray' , await this . managers . appNotificationsManager . isPeerLocalMuted ( this . chat . peerId , true ) ) ;
2021-09-01 20:17:06 +03:00
if ( this . goMentionUnreadBadge && this . chat . type === 'chat' ) {
2021-12-17 20:28:38 +04:00
const hasMentions = ! ! ( dialog ? . unread_mentions_count && dialog . unread_count ) ;
2021-09-01 20:17:06 +03:00
this . goMentionUnreadBadge . innerText = hasMentions ? '' + ( dialog . unread_mentions_count ) : '' ;
this . goMentionBtn . classList . toggle ( 'is-visible' , hasMentions ) ;
}
2021-08-03 04:44:13 +03:00
}
public saveDraft() {
if ( ! this . chat . peerId || this . editMsgId || this . chat . type === 'scheduled' ) return ;
const { value , entities } = getRichValue ( this . messageInputField . input ) ;
let draft : DraftMessage.draftMessage ;
if ( value . length || this . replyToMsgId ) {
draft = {
_ : 'draftMessage' ,
2022-06-17 20:01:43 +04:00
date : tsNow ( true ) ,
2021-08-03 04:44:13 +03:00
message : value ,
entities : entities.length ? entities : undefined ,
pFlags : {
no_webpage : this.noWebPage
} ,
reply_to_msg_id : this.replyToMsgId
} ;
}
2022-06-17 20:01:43 +04:00
this . managers . appDraftsManager . syncDraft ( this . chat . peerId , this . chat . threadId , draft ) ;
2021-08-03 04:44:13 +03:00
}
public destroy() {
//this.chat.log.error('Input destroying');
this . listenerSetter . removeAll ( ) ;
}
public cleanup ( helperToo = true ) {
if ( ! this . chat . peerId ) {
2022-06-17 20:01:43 +04:00
this . chatInput . classList . add ( 'hide' ) ;
2021-08-03 04:44:13 +03:00
this . goDownBtn . classList . add ( 'hide' ) ;
}
cancelSelection ( ) ;
this . lastTimeType = 0 ;
2022-01-15 02:01:50 +04:00
this . startParam = undefined ;
if ( this . toggleBotStartBtnDisability ) {
this . toggleBotStartBtnDisability ( ) ;
this . toggleBotStartBtnDisability = undefined ;
}
2021-08-03 04:44:13 +03:00
if ( this . messageInput ) {
this . clearInput ( ) ;
helperToo && this . clearHelper ( ) ;
}
}
2022-06-17 20:01:43 +04:00
public async setDraft ( draft? : MyDraftMessage , fromUpdate = true , force = false ) {
2021-08-03 04:44:13 +03:00
if ( ( ! force && ! isInputEmpty ( this . messageInput ) ) || this . chat . type === 'scheduled' ) return false ;
if ( ! draft ) {
2022-06-17 20:01:43 +04:00
draft = await this . managers . appDraftsManager . getDraft ( this . chat . peerId , this . chat . threadId ) ;
2021-08-03 04:44:13 +03:00
if ( ! draft ) {
2021-10-22 22:31:54 +04:00
if ( force ) { // this situation can only happen when sending message with clearDraft
2021-10-28 04:01:53 +03:00
/ * c o n s t h e i g h t = t h i s . c h a t I n p u t . g e t B o u n d i n g C l i e n t R e c t ( ) . h e i g h t ;
const willChangeHeight = 78 - height ;
this . willChangeHeight = willChangeHeight ; * /
if ( this . chat . container . classList . contains ( 'is-helper-active' ) ) {
this . t ( ) ;
}
this . messageInputField . inputFake . textContent = '' ;
this . messageInputField . onFakeInput ( false ) ;
2021-10-22 22:31:54 +04:00
( ( this . chat . bubbles . messagesQueuePromise || Promise . resolve ( ) ) as Promise < any > ) . then ( ( ) = > {
fastRaf ( ( ) = > {
this . onMessageSent ( ) ;
} ) ;
} ) ;
}
2021-08-03 04:44:13 +03:00
return false ;
}
}
2022-04-25 17:54:30 +03:00
const wrappedDraft = wrapDraft ( draft ) ;
if ( this . messageInputField . value === wrappedDraft && this . replyToMsgId === draft . reply_to_msg_id ) return false ;
2021-08-03 04:44:13 +03:00
2021-10-21 17:16:43 +04:00
if ( fromUpdate ) {
this . clearHelper ( ) ;
}
2021-08-03 04:44:13 +03:00
this . noWebPage = draft . pFlags . no_webpage ;
if ( draft . reply_to_msg_id ) {
this . initMessageReply ( draft . reply_to_msg_id ) ;
}
2022-04-25 17:54:30 +03:00
this . setInputValue ( wrappedDraft , fromUpdate , fromUpdate ) ;
2021-08-03 04:44:13 +03:00
return true ;
}
2022-06-17 20:01:43 +04:00
private createSendAs() {
this . sendAsPeerId = undefined ;
2021-08-03 04:44:13 +03:00
2022-06-17 20:01:43 +04:00
if ( this . chat . type === 'chat' || this . chat . type === 'discussion' ) {
let firstChange = true ;
this . sendAs = new ChatSendAs (
this . managers ,
( container , skipAnimation ) = > {
let useRafs = 0 ;
if ( ! container . parentElement ) {
this . newMessageWrapper . prepend ( container ) ;
useRafs = 2 ;
}
2021-08-03 04:44:13 +03:00
2022-06-17 20:01:43 +04:00
this . updateOffset ( 'as' , true , skipAnimation , useRafs ) ;
} ,
( sendAsPeerId ) = > {
this . sendAsPeerId = sendAsPeerId ;
2021-08-03 04:44:13 +03:00
2022-06-17 20:01:43 +04:00
// do not change placeholder earlier than finishPeerChange does
if ( firstChange ) {
firstChange = false ;
return ;
}
2021-08-03 04:44:13 +03:00
2022-06-17 20:01:43 +04:00
this . getPlaceholderKey ( ) . then ( ( key ) = > {
this . updateMessageInputPlaceholder ( key ) ;
} ) ;
}
) ;
} else {
this . sendAs = undefined ;
2021-11-29 17:51:29 +04:00
}
2022-06-17 20:01:43 +04:00
return this . sendAs ;
}
2021-08-03 04:44:13 +03:00
2022-06-17 20:01:43 +04:00
public async finishPeerChange ( startParam? : string ) {
const peerId = this . chat . peerId ;
2022-03-04 14:55:08 +02:00
2022-06-17 20:01:43 +04:00
const { forwardElements , btnScheduled , replyKeyboard , sendMenu , goDownBtn , chatInput , botCommandsToggle } = this ;
const previousSendAs = this . sendAs ;
const sendAs = this . createSendAs ( ) ;
2022-06-18 04:02:20 +04:00
const [
isBroadcast ,
canPinMessage ,
isBot ,
canSend ,
neededFakeContainer ,
ackedPeerFull ,
ackedScheduledMids ,
setSendAsCallback ,
filteredAttachMenuButtons
] = await Promise . all ( [
2022-06-17 20:01:43 +04:00
this . managers . appPeersManager . isBroadcast ( peerId ) ,
this . managers . appPeersManager . canPinMessage ( peerId ) ,
this . managers . appPeersManager . isBot ( peerId ) ,
this . chat . canSend ( ) ,
2022-06-28 19:49:52 +02:00
this . getNeededFakeContainer ( startParam ) ,
2022-06-17 20:01:43 +04:00
modifyAckedPromise ( this . managers . acknowledged . appProfileManager . getProfileByPeerId ( peerId ) ) ,
btnScheduled ? modifyAckedPromise ( this . managers . acknowledged . appMessagesManager . getScheduledMessages ( peerId ) ) : undefined ,
2022-06-18 04:02:20 +04:00
sendAs ? ( sendAs . setPeerId ( this . chat . peerId ) , sendAs . updateManual ( true ) ) : undefined ,
this . filterAttachMenuButtons ( )
2022-06-17 20:01:43 +04:00
] ) ;
const placeholderKey = this . messageInput ? await this . getPlaceholderKey ( ) : undefined ;
return ( ) = > {
// console.warn('[input] finishpeerchange start');
chatInput . classList . remove ( 'hide' ) ;
goDownBtn . classList . toggle ( 'is-broadcast' , isBroadcast ) ;
goDownBtn . classList . remove ( 'hide' ) ;
if ( this . goDownUnreadBadge ) {
this . setUnreadCount ( ) ;
}
if ( this . chat . type === 'pinned' ) {
chatInput . classList . toggle ( 'can-pin' , canPinMessage ) ;
} / * else if ( this . chat . type === 'chat' ) {
} * /
if ( forwardElements ) {
this . forwardWasDroppingAuthor = false ;
forwardElements . showCaption . checkboxField . setValueSilently ( true ) ;
forwardElements . showSender . checkboxField . setValueSilently ( true ) ;
}
if ( btnScheduled && ackedScheduledMids ) {
btnScheduled . classList . add ( 'hide' ) ;
2022-03-04 14:55:08 +02:00
const middleware = this . chat . bubbles . getMiddleware ( ) ;
2022-06-17 20:01:43 +04:00
callbackify ( ackedScheduledMids . result , ( mids ) = > {
if ( ! middleware ( ) || ! mids ) return ;
btnScheduled . classList . toggle ( 'hide' , ! mids . length ) ;
2022-03-04 14:55:08 +02:00
} ) ;
}
2022-06-17 20:01:43 +04:00
if ( this . newMessageWrapper ) {
this . updateOffset ( null , false , true ) ;
}
if ( botCommandsToggle ) {
this . hasBotCommands = undefined ;
this . botCommands . toggle ( true , undefined , true ) ;
this . updateBotCommandsToggle ( true ) ;
botCommandsToggle . remove ( ) ;
if ( isBot ) {
const middleware = this . chat . bubbles . getMiddleware ( ) ;
const result = ackedPeerFull . result ;
callbackify ( result , ( userFull ) = > {
if ( ! middleware ( ) ) return ;
this . updateBotCommands ( userFull as UserFull . userFull , ! ( result instanceof Promise ) ) ;
} ) ;
}
2022-02-20 17:37:37 +02:00
}
2021-08-03 04:44:13 +03:00
2022-06-17 20:01:43 +04:00
if ( previousSendAs ) {
previousSendAs . destroy ( ) ;
}
2022-01-13 02:02:14 +04:00
2022-06-17 20:01:43 +04:00
if ( setSendAsCallback ) {
setSendAsCallback ( ) ;
}
if ( replyKeyboard ) {
replyKeyboard . setPeer ( peerId ) ;
}
if ( sendMenu ) {
sendMenu . setPeerId ( peerId ) ;
}
if ( this . messageInput ) {
2022-06-18 04:02:20 +04:00
this . updateMessageInput ( canSend , placeholderKey , filteredAttachMenuButtons ) ;
2022-06-17 20:01:43 +04:00
} else if ( this . pinnedControlBtn ) {
this . pinnedControlBtn . append ( i18n ( canPinMessage ? 'Chat.Input.UnpinAll' : 'Chat.Pinned.DontShow' ) ) ;
}
// * testing
// this.startParam = this.appPeersManager.isBot(peerId) ? '123' : undefined;
this . startParam = startParam ;
this . _center ( neededFakeContainer , false ) ;
2022-01-15 02:01:50 +04:00
2022-06-17 20:01:43 +04:00
// console.warn('[input] finishpeerchange ends');
} ;
2021-08-03 04:44:13 +03:00
}
2022-03-04 14:55:08 +02:00
private updateOffset ( type : 'commands' | 'as' , forwards : boolean , skipAnimation? : boolean , useRafs? : number ) {
if ( type ) {
this . newMessageWrapper . dataset . offset = type ;
} else {
delete this . newMessageWrapper . dataset . offset ;
}
SetTransition ( this . newMessageWrapper , 'has-offset' , forwards , skipAnimation ? 0 : 300 , undefined , useRafs ) ;
}
private updateBotCommands ( userFull : UserFull.userFull , skipAnimation? : boolean ) {
2022-06-17 20:01:43 +04:00
this . hasBotCommands = ! ! userFull . bot_info ? . commands ? . length ;
2022-03-04 14:55:08 +02:00
this . updateBotCommandsToggle ( skipAnimation ) ;
}
private updateBotCommandsToggle ( skipAnimation? : boolean ) {
const { botCommandsToggle , hasBotCommands } = this ;
2022-04-14 02:41:37 +03:00
const show = ! ! hasBotCommands && this . isInputEmpty ( ) ;
2022-03-04 14:55:08 +02:00
if ( ! hasBotCommands ) {
2022-04-14 02:41:37 +03:00
if ( ! botCommandsToggle . parentElement ) {
return ;
}
2022-03-04 14:55:08 +02:00
botCommandsToggle . remove ( ) ;
}
const forwards = show ;
const useRafs = botCommandsToggle . parentElement ? 0 : 2 ;
if ( ! botCommandsToggle . parentElement ) {
this . newMessageWrapper . prepend ( botCommandsToggle ) ;
}
this . updateOffset ( 'commands' , forwards , skipAnimation , useRafs ) ;
}
2022-06-17 20:01:43 +04:00
private async getPlaceholderKey() {
const { peerId , threadId } = this . chat ;
let key : LangPackKey ;
if ( threadId ) {
key = 'Comment' ;
} else if ( await this . managers . appPeersManager . isBroadcast ( peerId ) ) {
key = 'ChannelBroadcast' ;
} else if (
( this . sendAsPeerId !== undefined && this . sendAsPeerId !== rootScope . myId ) ||
await this . managers . appMessagesManager . isAnonymousSending ( peerId )
) {
key = 'SendAnonymously' ;
} else {
key = 'Message' ;
2022-02-20 17:37:37 +02:00
}
2022-06-17 20:01:43 +04:00
return key ;
2022-02-20 17:37:37 +02:00
}
2022-06-17 20:01:43 +04:00
private updateMessageInputPlaceholder ( key : LangPackKey ) {
// console.warn('[input] update placeholder');
const i = I18n . weakMap . get ( this . messageInput ) as I18n . IntlElement ;
if ( ! i ) {
2022-02-20 17:37:37 +02:00
return ;
}
2022-02-22 14:40:02 +02:00
2022-06-17 20:01:43 +04:00
i . compareAndUpdate ( { key } ) ;
2022-02-22 14:40:02 +02:00
}
2022-06-18 04:02:20 +04:00
private filterAttachMenuButtons() {
2022-06-20 19:06:02 +04:00
if ( ! this . attachMenuButtons ) return ;
2022-06-18 04:02:20 +04:00
const { peerId , threadId } = this . chat ;
return filterAsync ( this . attachMenuButtons , ( button ) = > {
return button . verify ( peerId , threadId ) ;
} ) ;
}
public updateMessageInput ( canSend : boolean , placeholderKey : LangPackKey , visible : ChatInput [ 'attachMenuButtons' ] ) {
2022-02-22 14:40:02 +02:00
const { chatInput , attachMenu , messageInput } = this ;
const { peerId , threadId } = this . chat ;
const isHidden = chatInput . classList . contains ( 'is-hidden' ) ;
2022-06-17 20:01:43 +04:00
const willBeHidden = ! canSend ;
2022-02-22 14:40:02 +02:00
if ( isHidden !== willBeHidden ) {
chatInput . classList . add ( 'no-transition' ) ;
2022-06-17 20:01:43 +04:00
chatInput . classList . toggle ( 'is-hidden' , ! canSend ) ;
2022-02-22 14:40:02 +02:00
void chatInput . offsetLeft ; // reflow
chatInput . classList . remove ( 'no-transition' ) ;
}
2022-06-17 20:01:43 +04:00
this . updateMessageInputPlaceholder ( placeholderKey ) ;
2021-09-23 19:46:03 +04:00
2022-06-20 19:06:02 +04:00
this . attachMenuButtons && this . attachMenuButtons . forEach ( ( button ) = > {
2022-06-18 04:02:20 +04:00
button . element . classList . toggle ( 'hide' , ! visible . includes ( button ) ) ;
2021-08-03 04:44:13 +03:00
} ) ;
2022-06-17 20:01:43 +04:00
if ( ! canSend ) {
2021-12-02 17:44:18 +04:00
messageInput . removeAttribute ( 'contenteditable' ) ;
2021-08-03 04:44:13 +03:00
} else {
2021-12-02 17:44:18 +04:00
messageInput . setAttribute ( 'contenteditable' , 'true' ) ;
2021-08-03 04:44:13 +03:00
this . setDraft ( undefined , false ) ;
2021-12-02 17:44:18 +04:00
if ( ! messageInput . innerHTML ) {
2021-08-03 04:44:13 +03:00
this . messageInputField . onFakeInput ( ) ;
}
}
2022-06-20 19:06:02 +04:00
if ( attachMenu ) {
attachMenu . toggleAttribute ( 'disabled' , ! visible . length ) ;
attachMenu . classList . toggle ( 'btn-disabled' , ! visible . length ) ;
}
2021-08-03 04:44:13 +03:00
this . updateSendBtn ( ) ;
}
private attachMessageInputField() {
const oldInputField = this . messageInputField ;
this . messageInputField = new InputField ( {
placeholder : 'Message' ,
name : 'message' ,
animate : true
} ) ;
this . messageInputField . input . classList . replace ( 'input-field-input' , 'input-message-input' ) ;
this . messageInputField . inputFake . classList . replace ( 'input-field-input' , 'input-message-input' ) ;
this . messageInput = this . messageInputField . input ;
2021-08-03 20:12:09 +03:00
this . messageInput . classList . add ( 'no-scrollbar' ) ;
2021-08-03 04:44:13 +03:00
this . attachMessageInputListeners ( ) ;
2021-08-24 19:33:04 +03:00
if ( IS_STICKY_INPUT_BUGGED ) {
fixSafariStickyInputFocusing ( this . messageInput ) ;
}
2021-08-03 04:44:13 +03:00
if ( oldInputField ) {
oldInputField . input . replaceWith ( this . messageInputField . input ) ;
oldInputField . inputFake . replaceWith ( this . messageInputField . inputFake ) ;
} else {
this . inputMessageContainer . append ( this . messageInputField . input , this . messageInputField . inputFake ) ;
}
}
private attachMessageInputListeners() {
this . listenerSetter . add ( this . messageInput ) ( 'keydown' , ( e : KeyboardEvent ) = > {
2021-12-01 16:46:58 +04:00
const key = e . key ;
2021-08-03 04:44:13 +03:00
if ( isSendShortcutPressed ( e ) ) {
2021-10-22 22:31:54 +04:00
cancelEvent ( e ) ;
2021-08-03 04:44:13 +03:00
this . sendMessage ( ) ;
} else if ( e . ctrlKey || e . metaKey ) {
this . handleMarkdownShortcut ( e ) ;
2021-11-24 17:40:18 +04:00
} else if ( ( key === 'PageUp' || key === 'PageDown' ) && ! e . shiftKey ) { // * fix pushing page to left (Chrome Windows)
2021-08-03 04:44:13 +03:00
e . preventDefault ( ) ;
2021-11-24 17:40:18 +04:00
if ( key === 'PageUp' ) {
2021-08-03 04:44:13 +03:00
const range = document . createRange ( ) ;
const sel = window . getSelection ( ) ;
range . setStart ( this . messageInput . childNodes [ 0 ] || this . messageInput , 0 ) ;
range . collapse ( true ) ;
sel . removeAllRanges ( ) ;
sel . addRange ( range ) ;
} else {
placeCaretAtEnd ( this . messageInput ) ;
}
}
} ) ;
2021-09-26 17:59:10 +04:00
if ( IS_TOUCH_SUPPORTED ) {
2021-08-03 04:44:13 +03:00
attachClickEvent ( this . messageInput , ( e ) = > {
this . appImManager . selectTab ( 1 ) ; // * set chat tab for album orientation
//this.saveScroll();
emoticonsDropdown . toggle ( false ) ;
} , { listenerSetter : this.listenerSetter } ) ;
/ * t h i s . l i s t e n e r S e t t e r . a d d ( w i n d o w ) ( ' r e s i z e ' , ( ) = > {
this . restoreScroll ( ) ;
} ) ; * /
/ * i f ( i s S a f a r i ) {
this . listenerSetter . add ( this . messageInput ) ( 'mousedown' , ( ) = > {
window . requestAnimationFrame ( ( ) = > {
window . requestAnimationFrame ( ( ) = > {
emoticonsDropdown . toggle ( false ) ;
} ) ;
} ) ;
} ) ;
} * /
}
/ * t h i s . l i s t e n e r S e t t e r . a d d ( t h i s . m e s s a g e I n p u t ) ( ' b e f o r e i n p u t ' , ( e : E v e n t ) = > {
// * validate due to manual formatting through browser's context menu
const inputType = ( e as InputEvent ) . inputType ;
//console.log('message beforeinput event', e);
if ( inputType . indexOf ( 'format' ) === 0 ) {
//console.log('message beforeinput format', e, inputType, this.messageInput.innerHTML);
const markdownType = inputType . split ( 'format' ) [ 1 ] . toLowerCase ( ) as MarkdownType ;
if ( this . applyMarkdown ( markdownType ) ) {
cancelEvent ( e ) ; // * cancel legacy markdown event
}
}
} ) ; * /
this . listenerSetter . add ( this . messageInput ) ( 'input' , this . onMessageInput ) ;
this . listenerSetter . add ( this . messageInput ) ( 'keyup' , ( ) = > {
this . checkAutocomplete ( ) ;
} ) ;
if ( this . chat . type === 'chat' || this . chat . type === 'discussion' ) {
this . listenerSetter . add ( this . messageInput ) ( 'focusin' , ( ) = > {
if ( this . chat . bubbles . scrollable . loadedAll . bottom ) {
2022-06-17 20:01:43 +04:00
this . managers . appMessagesManager . readAllHistory ( this . chat . peerId , this . chat . threadId ) ;
2021-08-03 04:44:13 +03:00
}
} ) ;
}
}
private prepareDocumentExecute = ( ) = > {
this . executedHistory . push ( this . messageInput . innerHTML ) ;
return ( ) = > this . canUndoFromHTML = this . messageInput . innerHTML ;
} ;
private undoRedo = ( e : Event , type : 'undo' | 'redo' , needHTML : string ) = > {
cancelEvent ( e ) ; // cancel legacy event
let html = this . messageInput . innerHTML ;
if ( html && html !== needHTML ) {
this . lockRedo = true ;
let sameHTMLTimes = 0 ;
do {
document . execCommand ( type , false , null ) ;
const currentHTML = this . messageInput . innerHTML ;
if ( html === currentHTML ) {
if ( ++ sameHTMLTimes > 2 ) { // * unlink, removeFormat (а может и нет, случай: заболдить подчёркнутый текст (выделить ровно е г о ), попробовать отменить)
break ;
}
} else {
sameHTMLTimes = 0 ;
}
html = currentHTML ;
} while ( html !== needHTML ) ;
this . lockRedo = false ;
}
} ;
public applyMarkdown ( type : MarkdownType , href? : string ) {
2022-04-16 19:21:50 +03:00
const MONOSPACE_FONT = 'var(--font-monospace)' ;
const SPOILER_FONT = 'spoiler' ;
2021-08-03 04:44:13 +03:00
const commandsMap : Partial < { [ key in typeof type ] : string | ( ( ) = > void ) } > = {
bold : 'Bold' ,
italic : 'Italic' ,
underline : 'Underline' ,
strikethrough : 'Strikethrough' ,
2022-04-16 19:21:50 +03:00
monospace : ( ) = > document . execCommand ( 'fontName' , false , MONOSPACE_FONT ) ,
2022-01-18 19:40:07 +04:00
link : href ? ( ) = > document . execCommand ( 'createLink' , false , href ) : ( ) = > document . execCommand ( 'unlink' , false , null ) ,
2022-04-16 19:21:50 +03:00
spoiler : ( ) = > document . execCommand ( 'fontName' , false , SPOILER_FONT )
2021-08-03 04:44:13 +03:00
} ;
if ( ! commandsMap [ type ] ) {
return false ;
}
const command = commandsMap [ type ] ;
//type = 'monospace';
const saveExecuted = this . prepareDocumentExecute ( ) ;
const executed : any [ ] = [ ] ;
/ * *
* * clear previous formatting , due to Telegram ' s inability to handle several entities
* /
/ * c o n s t c h e c k F o r S i n g l e = ( ) = > {
const nodes = getSelectedNodes ( ) ;
//console.log('Using formatting:', commandsMap[type], nodes, this.executedHistory);
2022-06-17 20:01:43 +04:00
const parents = [ . . . new Set ( nodes . map ( ( node ) = > node . parentNode ) ) ] ;
//const differentParents = !!nodes.find((node) => node.parentNode !== firstParent);
2021-08-03 04:44:13 +03:00
const differentParents = parents . length > 1 ;
let notSingle = false ;
if ( differentParents ) {
notSingle = true ;
} else {
const node = nodes [ 0 ] ;
if ( node && ( node . parentNode as HTMLElement ) !== this . messageInput && ( node . parentNode . parentNode as HTMLElement ) !== this . messageInput ) {
notSingle = true ;
}
}
if ( notSingle ) {
//if(type === 'monospace') {
executed . push ( document . execCommand ( 'styleWithCSS' , false , 'true' ) ) ;
//}
executed . push ( document . execCommand ( 'unlink' , false , null ) ) ;
executed . push ( document . execCommand ( 'removeFormat' , false , null ) ) ;
executed . push ( typeof ( command ) === 'function' ? command ( ) : document . execCommand ( command , false , null ) ) ;
//if(type === 'monospace') {
executed . push ( document . execCommand ( 'styleWithCSS' , false , 'false' ) ) ;
//}
}
} ; * /
executed . push ( document . execCommand ( 'styleWithCSS' , false , 'true' ) ) ;
2022-01-25 21:26:51 +04:00
if ( type === 'monospace' || type === 'spoiler' ) {
2021-08-03 04:44:13 +03:00
let haveThisType = false ;
//executed.push(document.execCommand('styleWithCSS', false, 'true'));
const selection = window . getSelection ( ) ;
if ( ! selection . isCollapsed ) {
const range = selection . getRangeAt ( 0 ) ;
const tag = markdownTags [ type ] ;
const node = range . commonAncestorContainer ;
if ( ( node . parentNode as HTMLElement ) . matches ( tag . match ) || ( node instanceof HTMLElement && node . matches ( tag . match ) ) ) {
haveThisType = true ;
}
}
//executed.push(document.execCommand('removeFormat', false, null));
if ( haveThisType ) {
2022-04-16 19:21:50 +03:00
executed . push ( this . resetCurrentFormatting ( ) ) ;
2021-08-03 04:44:13 +03:00
} else {
executed . push ( typeof ( command ) === 'function' ? command ( ) : document . execCommand ( command , false , null ) ) ;
}
} else {
executed . push ( typeof ( command ) === 'function' ? command ( ) : document . execCommand ( command , false , null ) ) ;
}
executed . push ( document . execCommand ( 'styleWithCSS' , false , 'false' ) ) ;
//checkForSingle();
saveExecuted ( ) ;
if ( this . appImManager . markupTooltip ) {
this . appImManager . markupTooltip . setActiveMarkupButton ( ) ;
}
return true ;
}
2022-04-16 19:21:50 +03:00
private resetCurrentFormatting() {
return document . execCommand ( 'fontName' , false , 'Roboto' ) ;
}
2021-08-03 04:44:13 +03:00
private handleMarkdownShortcut = ( e : KeyboardEvent ) = > {
// console.log('handleMarkdownShortcut', e);
const formatKeys : { [ key : string ] : MarkdownType } = {
2021-12-01 16:46:58 +04:00
'KeyB' : 'bold' ,
'KeyI' : 'italic' ,
'KeyU' : 'underline' ,
'KeyS' : 'strikethrough' ,
2022-01-18 19:40:07 +04:00
'KeyM' : 'monospace' ,
'KeyP' : 'spoiler'
2021-08-03 04:44:13 +03:00
} ;
2021-11-24 17:40:18 +04:00
if ( this . appImManager . markupTooltip ) {
2021-12-01 16:46:58 +04:00
formatKeys [ 'KeyK' ] = 'link' ;
2021-11-24 17:40:18 +04:00
}
2021-12-01 16:46:58 +04:00
const code = e . code ;
const applyMarkdown = formatKeys [ code ] ;
2021-11-24 17:40:18 +04:00
2021-08-03 04:44:13 +03:00
const selection = document . getSelection ( ) ;
2021-11-24 17:40:18 +04:00
if ( selection . toString ( ) . trim ( ) . length && applyMarkdown ) {
// * костыльчик
2021-12-01 16:46:58 +04:00
if ( code === 'KeyK' ) {
2021-11-24 17:40:18 +04:00
this . appImManager . markupTooltip . showLinkEditor ( ) ;
} else {
this . applyMarkdown ( applyMarkdown ) ;
2021-08-03 04:44:13 +03:00
}
2021-11-24 17:40:18 +04:00
cancelEvent ( e ) ; // cancel legacy event
2021-08-03 04:44:13 +03:00
}
//return;
2021-12-01 16:46:58 +04:00
if ( code === 'KeyZ' ) {
2021-08-03 04:44:13 +03:00
let html = this . messageInput . innerHTML ;
if ( e . shiftKey ) {
if ( this . undoHistory . length ) {
this . executedHistory . push ( html ) ;
html = this . undoHistory . pop ( ) ;
this . undoRedo ( e , 'redo' , html ) ;
html = this . messageInput . innerHTML ;
this . canRedoFromHTML = this . undoHistory . length ? html : '' ;
this . canUndoFromHTML = html ;
}
} else {
// * подождём, когда пользователь сам восстановит поле до нужного состояния, которое стало сразу после saveExecuted
if ( this . executedHistory . length && ( ! this . canUndoFromHTML || html === this . canUndoFromHTML ) ) {
this . undoHistory . push ( html ) ;
html = this . executedHistory . pop ( ) ;
this . undoRedo ( e , 'undo' , html ) ;
// * поставим новое состояние чтобы снова подождать, если пользователь изменит что-то, и потом попробует откатить до предыдущего состояния
this . canUndoFromHTML = this . canRedoFromHTML = this . messageInput . innerHTML ;
}
}
}
} ;
private onMessageInput = ( e? : Event ) = > {
// * validate due to manual formatting through browser's context menu
/ * c o n s t i n p u t T y p e = ( e a s I n p u t E v e n t ) . i n p u t T y p e ;
console . log ( 'message input event' , e ) ;
if ( inputType === 'formatBold' ) {
console . log ( 'message input format' , this . messageInput . innerHTML ) ;
cancelEvent ( e ) ;
}
if ( ! isSelectionSingle ( ) ) {
alert ( 'not single' ) ;
} * /
//console.log('messageInput input', this.messageInput.innerText);
//const value = this.messageInput.innerText;
const { value : richValue , entities : markdownEntities , caretPos } = getRichValueWithCaret ( this . messageInputField . input ) ;
2022-04-25 17:54:30 +03:00
//const entities = parseEntities(value);
const value = parseMarkdown ( richValue , markdownEntities , true ) ;
const entities = mergeEntities ( markdownEntities , parseEntities ( value ) ) ;
2021-08-03 04:44:13 +03:00
//this.chat.log('messageInput entities', richValue, value, markdownEntities, caretPos);
if ( this . canRedoFromHTML && ! this . lockRedo && this . messageInput . innerHTML !== this . canRedoFromHTML ) {
this . canRedoFromHTML = '' ;
this . undoHistory . length = 0 ;
}
2022-06-17 20:01:43 +04:00
const urlEntities : Array < MessageEntity.messageEntityUrl | MessageEntity.messageEntityTextUrl > = ( ! this . editMessage ? . media || this . editMessage . media . _ === 'messageMediaWebPage' ) && entities . filter ( ( e ) = > e . _ === 'messageEntityUrl' || e . _ === 'messageEntityTextUrl' ) as any ;
2021-08-03 04:44:13 +03:00
if ( urlEntities . length ) {
for ( const entity of urlEntities ) {
let url : string ;
if ( entity . _ === 'messageEntityTextUrl' ) {
url = entity . url ;
} else {
url = richValue . slice ( entity . offset , entity . offset + entity . length ) ;
if ( ! ( url . includes ( 'http://' ) || url . includes ( 'https://' ) ) ) {
continue ;
}
}
//console.log('messageInput url:', url);
if ( this . lastUrl !== url ) {
this . lastUrl = url ;
2021-08-09 21:47:45 +03:00
// this.willSendWebPage = null;
2022-06-17 20:01:43 +04:00
const promise = this . getWebPagePromise = this . managers . appWebPagesManager . getWebPage ( url ) . then ( ( webpage ) = > {
2021-08-09 21:47:45 +03:00
if ( this . getWebPagePromise === promise ) this . getWebPagePromise = undefined ;
if ( this . lastUrl !== url ) return ;
2021-08-03 04:44:13 +03:00
if ( webpage . _ === 'webPage' ) {
//console.log('got webpage: ', webpage);
this . setTopInfo ( 'webpage' , ( ) = > { } , webpage . site_name || webpage . title || 'Webpage' , webpage . description || webpage . url || '' ) ;
delete this . noWebPage ;
this . willSendWebPage = webpage ;
2021-08-09 21:47:45 +03:00
} else if ( this . willSendWebPage ) {
this . onHelperCancel ( ) ;
2021-08-03 04:44:13 +03:00
}
} ) ;
}
break ;
}
} else if ( this . lastUrl ) {
this . lastUrl = '' ;
delete this . noWebPage ;
this . willSendWebPage = null ;
if ( this . helperType ) {
this . helperFunc ( ) ;
} else {
this . clearHelper ( ) ;
}
}
2022-03-04 14:55:08 +02:00
const isEmpty = ! richValue . trim ( ) ;
if ( isEmpty ) {
2021-08-03 04:44:13 +03:00
if ( this . lastTimeType ) {
2022-06-17 20:01:43 +04:00
this . managers . appMessagesManager . setTyping ( this . chat . peerId , { _ : 'sendMessageCancelAction' } ) ;
2021-08-03 04:44:13 +03:00
}
2021-08-24 19:33:04 +03:00
if ( this . appImManager . markupTooltip ) {
this . appImManager . markupTooltip . hide ( ) ;
}
2022-04-16 19:21:50 +03:00
// * Chrome has a bug - it will preserve the formatting if the input with monospace text is cleared
// * so have to reset formatting
if ( document . activeElement === this . messageInput ) {
// document.execCommand('styleWithCSS', false, 'true');
2022-04-16 19:55:09 +03:00
setTimeout ( ( ) = > {
if ( document . activeElement === this . messageInput ) {
this . resetCurrentFormatting ( ) ;
}
} , 0 ) ;
2022-04-16 19:21:50 +03:00
// document.execCommand('styleWithCSS', false, 'false');
}
2021-08-03 04:44:13 +03:00
} else {
const time = Date . now ( ) ;
if ( time - this . lastTimeType >= 6000 ) {
this . lastTimeType = time ;
2022-06-17 20:01:43 +04:00
this . managers . appMessagesManager . setTyping ( this . chat . peerId , { _ : 'sendMessageTypingAction' } ) ;
2021-08-03 04:44:13 +03:00
}
2022-03-04 14:55:08 +02:00
if ( this . botCommands ) {
this . botCommands . toggle ( true ) ;
}
}
if ( this . botCommands ) {
this . updateBotCommandsToggle ( ) ;
2021-08-03 04:44:13 +03:00
}
if ( ! this . editMsgId ) {
this . saveDraftDebounced ( ) ;
}
this . checkAutocomplete ( richValue , caretPos , entities ) ;
this . updateSendBtn ( ) ;
} ;
public insertAtCaret ( insertText : string , insertEntity? : MessageEntity , isHelper = true ) {
const { value : fullValue , caretPos , entities } = getRichValueWithCaret ( this . messageInput ) ;
const pos = caretPos >= 0 ? caretPos : fullValue.length ;
const prefix = fullValue . substr ( 0 , pos ) ;
const suffix = fullValue . substr ( pos ) ;
const matches = isHelper ? prefix . match ( ChatInput . AUTO_COMPLETE_REG_EXP ) : null ;
const matchIndex = matches ? matches . index + ( matches [ 0 ] . length - matches [ 2 ] . length ) : prefix . length ;
const newPrefix = prefix . slice ( 0 , matchIndex ) ;
const newValue = newPrefix + insertText + suffix ;
// merge emojis
2022-04-25 17:54:30 +03:00
const hadEntities = parseEntities ( fullValue ) ;
mergeEntities ( entities , hadEntities ) ;
2021-08-03 04:44:13 +03:00
// max for additional whitespace
const insertLength = insertEntity ? Math . max ( insertEntity . length , insertText . length ) : insertText . length ;
const addEntities : MessageEntity [ ] = [ ] ;
if ( insertEntity ) {
addEntities . push ( insertEntity ) ;
insertEntity . offset = matchIndex ;
}
// add offset to entities next to emoji
2021-11-11 02:58:04 +04:00
const diff = matches ? insertLength - matches [ 2 ] . length : insertLength ;
2022-06-17 20:01:43 +04:00
entities . forEach ( ( entity ) = > {
2021-08-03 04:44:13 +03:00
if ( entity . offset >= matchIndex ) {
entity . offset += diff ;
}
} ) ;
2022-04-25 17:54:30 +03:00
mergeEntities ( entities , addEntities ) ;
2021-08-03 04:44:13 +03:00
2021-11-11 02:58:04 +04:00
if ( /* caretPos !== -1 && caretPos !== fullValue.length */ true ) {
const caretEntity : MessageEntity.messageEntityCaret = {
_ : 'messageEntityCaret' ,
offset : matchIndex + insertLength ,
length : 0
} ;
let insertCaretAtIndex = 0 ;
for ( let length = entities . length ; insertCaretAtIndex < length ; ++ insertCaretAtIndex ) {
const entity = entities [ insertCaretAtIndex ] ;
if ( entity . offset > caretEntity . offset ) {
break ;
}
}
entities . splice ( insertCaretAtIndex , 0 , caretEntity ) ;
}
2021-08-03 04:44:13 +03:00
//const saveExecuted = this.prepareDocumentExecute();
// can't exec .value here because it will instantly check for autocomplete
2022-04-25 17:54:30 +03:00
const value = documentFragmentToHTML ( wrapDraftText ( newValue , { entities } ) ) ;
2021-11-11 02:58:04 +04:00
this . messageInputField . setValueSilently ( value , true ) ;
2021-08-03 04:44:13 +03:00
const caret = this . messageInput . querySelector ( '.composer-sel' ) ;
2021-11-11 02:58:04 +04:00
if ( caret ) {
setCaretAt ( caret ) ;
caret . remove ( ) ;
}
2021-08-03 04:44:13 +03:00
// but it's needed to be checked only here
this . onMessageInput ( ) ;
//saveExecuted();
2022-04-25 17:54:30 +03:00
//document.execCommand('insertHTML', true, wrapEmojiText(emoji));
2021-08-03 04:44:13 +03:00
}
public onEmojiSelected = ( emoji : string , autocomplete : boolean ) = > {
2022-04-25 17:54:30 +03:00
this . insertAtCaret ( emoji , getEmojiEntityFromEmoji ( emoji ) , autocomplete ) ;
2021-08-03 04:44:13 +03:00
} ;
2022-06-17 20:01:43 +04:00
private async checkAutocomplete ( value? : string , caretPos? : number , entities? : MessageEntity [ ] ) {
2021-08-03 04:44:13 +03:00
//return;
if ( value === undefined ) {
const r = getRichValueWithCaret ( this . messageInputField . input , true ) ;
value = r . value ;
caretPos = r . caretPos ;
entities = r . entities ;
}
if ( caretPos === - 1 ) {
caretPos = value . length ;
}
if ( entities === undefined ) {
2022-04-25 17:54:30 +03:00
const _value = parseMarkdown ( value , entities , true ) ;
entities = mergeEntities ( entities , parseEntities ( _value ) ) ;
2021-08-03 04:44:13 +03:00
}
2022-01-08 16:52:14 +04:00
value = value . slice ( 0 , caretPos ) ;
2021-08-03 04:44:13 +03:00
if ( this . previousQuery === value ) {
return ;
}
this . previousQuery = value ;
const matches = value . match ( ChatInput . AUTO_COMPLETE_REG_EXP ) ;
let foundHelper : AutocompleteHelper ;
if ( matches ) {
const entity = entities [ 0 ] ;
2021-08-27 03:09:15 +03:00
let query = matches [ 2 ] ;
2021-08-03 04:44:13 +03:00
const firstChar = query [ 0 ] ;
if ( this . stickersHelper &&
rootScope . settings . stickers . suggest &&
2022-06-17 20:01:43 +04:00
await this . chat . canSend ( 'send_stickers' ) &&
2021-08-03 04:44:13 +03:00
entity ? . _ === 'messageEntityEmoji' && entity . length === value . length && ! entity . offset ) {
foundHelper = this . stickersHelper ;
this . stickersHelper . checkEmoticon ( value ) ;
} else if ( firstChar === '@' ) { // mentions
2022-06-17 20:01:43 +04:00
const topMsgId = this . chat . threadId ? getServerMessageId ( this . chat . threadId ) : undefined ;
if ( await this . mentionsHelper . checkQuery ( query , this . chat . peerId . isUser ( ) ? NULL_PEER_ID : this.chat.peerId , topMsgId ) ) {
2021-08-03 04:44:13 +03:00
foundHelper = this . mentionsHelper ;
}
} else if ( ! matches [ 1 ] && firstChar === '/' ) { // commands
2022-06-17 20:01:43 +04:00
if ( await this . commandsHelper . checkQuery ( query , this . chat . peerId ) ) {
2021-08-03 04:44:13 +03:00
foundHelper = this . commandsHelper ;
}
} else if ( rootScope . settings . emoji . suggest ) { // emoji
2021-08-27 03:09:15 +03:00
query = query . replace ( /^\s*/ , '' ) ;
if ( ! value . match ( /^\s*:(.+):\s*$/ ) && ! value . match ( /:[;!@#$%^&*()-=|]/ ) && query ) {
2021-08-03 04:44:13 +03:00
foundHelper = this . emojiHelper ;
this . emojiHelper . checkQuery ( query , firstChar ) ;
}
}
}
foundHelper = this . checkInlineAutocomplete ( value , foundHelper ) ;
this . autocompleteHelperController . hideOtherHelpers ( foundHelper ) ;
}
private checkInlineAutocomplete ( value : string , foundHelper? : AutocompleteHelper ) : AutocompleteHelper {
let needPlaceholder = false ;
if ( ! foundHelper ) {
const inlineMatch = value . match ( /^@([a-zA-Z\\d_]{3,32})\s/ ) ;
if ( inlineMatch ) {
const username = inlineMatch [ 1 ] ;
const query = value . slice ( inlineMatch [ 0 ] . length ) ;
needPlaceholder = inlineMatch [ 0 ] . length === value . length ;
foundHelper = this . inlineHelper ;
if ( ! this . btnPreloader ) {
this . btnPreloader = ButtonIcon ( 'none btn-preloader float show disable-hover' , { noRipple : true } ) ;
putPreloader ( this . btnPreloader , true ) ;
this . inputMessageContainer . parentElement . insertBefore ( this . btnPreloader , this . inputMessageContainer . nextSibling ) ;
} else {
SetTransition ( this . btnPreloader , 'show' , true , 400 ) ;
}
this . inlineHelper . checkQuery ( this . chat . peerId , username , query ) . then ( ( { user , renderPromise } ) = > {
2021-08-03 20:12:09 +03:00
if ( needPlaceholder && user . bot_inline_placeholder ) {
2021-08-03 04:44:13 +03:00
this . messageInput . dataset . inlinePlaceholder = user . bot_inline_placeholder ;
}
renderPromise . then ( ( ) = > {
SetTransition ( this . btnPreloader , 'show' , false , 400 ) ;
} ) ;
} ) . catch ( noop ) ;
}
}
if ( ! needPlaceholder ) {
delete this . messageInput . dataset . inlinePlaceholder ;
}
if ( foundHelper !== this . inlineHelper ) {
if ( this . btnPreloader ) {
SetTransition ( this . btnPreloader , 'show' , false , 400 ) ;
}
}
return foundHelper ;
}
2022-01-13 02:02:14 +04:00
private setRecording ( value : boolean ) {
if ( this . recording === value ) {
return ;
}
SetTransition ( this . chatInput , 'is-recording' , value , 200 ) ;
this . recording = value ;
this . updateSendBtn ( ) ;
}
2022-06-17 20:01:43 +04:00
private onBtnSendClick = async ( e : Event ) = > {
2021-08-03 04:44:13 +03:00
cancelEvent ( e ) ;
2021-09-12 08:12:39 +03:00
if ( ! this . recorder || this . recording || ! this . isInputEmpty ( ) || this . forwarding || this . editMsgId ) {
2021-08-03 04:44:13 +03:00
if ( this . recording ) {
if ( ( Date . now ( ) - this . recordStartTime ) < RECORD_MIN_TIME ) {
this . onCancelRecordClick ( ) ;
} else {
this . recorder . stop ( ) ;
}
} else {
this . sendMessage ( ) ;
}
} else {
2022-06-17 20:01:43 +04:00
if ( this . chat . peerId . isAnyChat ( ) && ! ( await this . chat . canSend ( 'send_media' ) ) ) {
2021-08-03 04:44:13 +03:00
toast ( POSTING_MEDIA_NOT_ALLOWED ) ;
return ;
}
this . chatInput . classList . add ( 'is-locked' ) ;
blurActiveElement ( ) ;
2021-10-07 18:48:18 +04:00
2021-08-03 04:44:13 +03:00
this . recorder . start ( ) . then ( ( ) = > {
2021-10-07 18:48:18 +04:00
this . releaseMediaPlayback = appMediaPlaybackController . setSingleMedia ( ) ;
2021-08-03 04:44:13 +03:00
this . recordCanceled = false ;
2022-01-13 02:02:14 +04:00
this . setRecording ( true ) ;
2021-08-03 04:44:13 +03:00
opusDecodeController . setKeepAlive ( true ) ;
2021-09-23 20:27:10 +04:00
const showDiscardPopup = ( ) = > {
new PopupPeer ( 'popup-cancel-record' , {
titleLangKey : 'DiscardVoiceMessageTitle' ,
descriptionLangKey : 'DiscardVoiceMessageDescription' ,
buttons : [ {
langKey : 'DiscardVoiceMessageAction' ,
callback : ( ) = > {
simulateClickEvent ( this . btnCancelRecord ) ;
}
} , {
langKey : 'Continue' ,
isCancel : true
} ]
} ) . show ( ) ;
} ;
2021-08-03 04:44:13 +03:00
2021-09-23 20:11:33 +04:00
this . recordingOverlayListener = this . listenerSetter . add ( document . body ) ( 'mousedown' , ( e ) = > {
if ( ! findUpClassName ( e . target , 'chat-input' ) && ! findUpClassName ( e . target , 'popup-cancel-record' ) ) {
cancelEvent ( e ) ;
2021-09-23 20:27:10 +04:00
showDiscardPopup ( ) ;
2021-09-23 20:11:33 +04:00
}
} , { capture : true , passive : false } ) as any ;
2021-09-23 20:27:10 +04:00
appNavigationController . pushItem ( this . recordingNavigationItem = {
type : 'voice' ,
onPop : ( ) = > {
setTimeout ( ( ) = > {
showDiscardPopup ( ) ;
} , 0 ) ;
return false ;
}
} ) ;
2021-08-03 04:44:13 +03:00
this . recordStartTime = Date . now ( ) ;
const sourceNode : MediaStreamAudioSourceNode = this . recorder . sourceNode ;
const context = sourceNode . context ;
const analyser = context . createAnalyser ( ) ;
sourceNode . connect ( analyser ) ;
//analyser.connect(context.destination);
analyser . fftSize = 32 ;
const frequencyData = new Uint8Array ( analyser . frequencyBinCount ) ;
const max = frequencyData . length * 255 ;
const min = 54 / 150 ;
let r = ( ) = > {
if ( ! this . recording ) return ;
analyser . getByteFrequencyData ( frequencyData ) ;
let sum = 0 ;
2022-06-17 20:01:43 +04:00
frequencyData . forEach ( ( value ) = > {
2021-08-03 04:44:13 +03:00
sum += value ;
} ) ;
let percents = Math . min ( 1 , ( sum / max ) + min ) ;
//console.log('frequencyData', frequencyData, percents);
this . recordRippleEl . style . transform = ` scale( ${ percents } ) ` ;
let diff = Date . now ( ) - this . recordStartTime ;
let ms = diff % 1000 ;
2022-03-26 03:55:57 +02:00
let formatted = toHHMMSS ( diff / 1000 ) + ',' + ( '00' + Math . round ( ms / 10 ) ) . slice ( - 2 ) ;
2021-08-03 04:44:13 +03:00
this . recordTimeEl . innerText = formatted ;
2021-09-23 17:41:02 +04:00
fastRaf ( r ) ;
2021-08-03 04:44:13 +03:00
} ;
r ( ) ;
} ) . catch ( ( e : Error ) = > {
switch ( e . name as string ) {
case 'NotAllowedError' : {
toast ( 'Please allow access to your microphone' ) ;
break ;
}
case 'NotReadableError' : {
toast ( e . message ) ;
break ;
}
default :
console . error ( 'Recorder start error:' , e , e . name , e . message ) ;
toast ( e . message ) ;
break ;
}
2022-01-13 02:02:14 +04:00
this . setRecording ( false ) ;
this . chatInput . classList . remove ( 'is-locked' ) ;
2021-08-03 04:44:13 +03:00
} ) ;
}
} ;
2022-06-28 02:44:25 +02:00
private onHelperCancel = async ( e? : Event , force? : boolean ) = > {
2021-08-03 04:44:13 +03:00
if ( e ) {
cancelEvent ( e ) ;
}
if ( this . willSendWebPage ) {
const lastUrl = this . lastUrl ;
let needReturn = false ;
if ( this . helperType ) {
//if(this.helperFunc) {
2022-06-28 02:44:25 +02:00
await this . helperFunc ( ) ;
2021-08-03 04:44:13 +03:00
//}
needReturn = true ;
}
// * restore values
this . lastUrl = lastUrl ;
this . noWebPage = true ;
this . willSendWebPage = null ;
if ( needReturn ) return ;
}
2021-10-21 17:16:43 +04:00
if ( this . helperType === 'edit' && ! force ) {
const message = this . editMessage
2022-04-25 17:54:30 +03:00
const value = parseMarkdown ( this . messageInputField . value , [ ] ) ;
2021-10-21 17:16:43 +04:00
if ( message . message !== value ) {
new PopupPeer ( 'discard-editing' , {
buttons : [ {
langKey : 'Alert.Confirm.Discard' ,
callback : ( ) = > {
this . onHelperCancel ( undefined , true ) ;
}
} ] ,
descriptionLangKey : 'Chat.Edit.Cancel.Text'
} ) . show ( ) ;
return ;
}
}
2021-08-03 04:44:13 +03:00
this . clearHelper ( ) ;
this . updateSendBtn ( ) ;
} ;
private onHelperClick = ( e : Event ) = > {
cancelEvent ( e ) ;
2021-11-29 17:51:29 +04:00
if ( ! findUpClassName ( e . target , 'reply' ) ) return ;
2021-08-03 04:44:13 +03:00
if ( this . helperType === 'forward' ) {
2021-12-02 17:44:18 +04:00
const { forwardElements } = this ;
if ( forwardElements && IS_TOUCH_SUPPORTED && ! forwardElements . container . classList . contains ( 'active' ) ) {
2022-06-17 20:01:43 +04:00
contextMenuController . openBtnMenu ( forwardElements . container ) ;
2021-11-29 17:51:29 +04:00
}
2021-08-03 04:44:13 +03:00
} else if ( this . helperType === 'reply' ) {
this . chat . setMessageId ( this . replyToMsgId ) ;
} else if ( this . helperType === 'edit' ) {
this . chat . setMessageId ( this . editMsgId ) ;
}
} ;
2021-11-29 17:51:29 +04:00
private changeForwardRecipient() {
if ( this . helperWaitingForward ) return ;
this . helperWaitingForward = true ;
2022-04-03 17:48:38 +03:00
const forwarding = copy ( this . forwarding ) ;
2021-11-29 17:51:29 +04:00
const helperFunc = this . helperFunc ;
this . clearHelper ( ) ;
this . updateSendBtn ( ) ;
let selected = false ;
2022-04-03 17:48:38 +03:00
const popup = new PopupForward ( forwarding , ( ) = > {
2021-11-29 17:51:29 +04:00
selected = true ;
} ) ;
popup . addEventListener ( 'close' , ( ) = > {
this . helperWaitingForward = false ;
if ( ! selected ) {
helperFunc ( ) ;
}
} ) ;
}
2022-06-17 20:01:43 +04:00
public async clearInput ( canSetDraft = true , fireEvent = true , clearValue = '' ) {
2021-09-26 17:59:10 +04:00
if ( document . activeElement === this . messageInput && IS_MOBILE_SAFARI ) { // fix first char uppercase
2021-08-03 04:44:13 +03:00
const i = document . createElement ( 'input' ) ;
document . body . append ( i ) ;
fixSafariStickyInput ( i ) ;
this . messageInputField . setValueSilently ( clearValue ) ;
fixSafariStickyInput ( this . messageInput ) ;
i . remove ( ) ;
} else {
this . messageInputField . setValueSilently ( clearValue ) ;
}
2021-09-26 17:59:10 +04:00
if ( IS_TOUCH_SUPPORTED ) {
2021-08-03 04:44:13 +03:00
//this.messageInput.innerText = '';
} else {
//this.attachMessageInputField();
//this.messageInput.innerText = '';
// clear executions
this . canRedoFromHTML = '' ;
this . undoHistory . length = 0 ;
this . executedHistory . length = 0 ;
this . canUndoFromHTML = '' ;
}
let set = false ;
if ( canSetDraft ) {
2022-06-17 20:01:43 +04:00
set = await this . setDraft ( undefined , false ) ;
2021-08-03 04:44:13 +03:00
}
if ( ! set && fireEvent ) {
this . onMessageInput ( ) ;
}
}
public isInputEmpty() {
return isInputEmpty ( this . messageInput ) ;
}
public updateSendBtn() {
let icon : 'send' | 'record' | 'edit' | 'schedule' ;
const isInputEmpty = this . isInputEmpty ( ) ;
if ( this . editMsgId ) icon = 'edit' ;
2021-09-12 08:12:39 +03:00
else if ( ! this . recorder || this . recording || ! isInputEmpty || this . forwarding ) icon = this . chat . type === 'scheduled' ? 'schedule' : 'send' ;
2021-08-03 04:44:13 +03:00
else icon = 'record' ;
2022-06-17 20:01:43 +04:00
[ 'send' , 'record' , 'edit' , 'schedule' ] . forEach ( ( i ) = > {
2021-08-03 04:44:13 +03:00
this . btnSend . classList . toggle ( i , icon === i ) ;
} ) ;
if ( this . btnScheduled ) {
this . btnScheduled . classList . toggle ( 'show' , isInputEmpty ) ;
}
if ( this . btnToggleReplyMarkup ) {
this . btnToggleReplyMarkup . classList . toggle ( 'show' , isInputEmpty ) ;
}
}
public onMessageSent ( clearInput = true , clearReply? : boolean ) {
if ( this . chat . type !== 'scheduled' ) {
2022-06-17 20:01:43 +04:00
this . managers . appMessagesManager . readAllHistory ( this . chat . peerId , this . chat . threadId , true ) ;
2021-08-03 04:44:13 +03:00
}
this . scheduleDate = undefined ;
this . sendSilent = undefined ;
const value = this . messageInputField . value ;
2022-04-25 17:54:30 +03:00
const entities = parseEntities ( value ) ;
2022-06-17 20:01:43 +04:00
const emojiEntities : MessageEntity.messageEntityEmoji [ ] = entities . filter ( ( entity ) = > entity . _ === 'messageEntityEmoji' ) as any ;
emojiEntities . forEach ( ( entity ) = > {
2021-08-03 04:44:13 +03:00
const emoji = emojiFromCodePoints ( entity . unicode ) ;
2022-06-17 20:01:43 +04:00
this . managers . appEmojiManager . pushRecentEmoji ( emoji ) ;
2021-08-03 04:44:13 +03:00
} ) ;
if ( clearInput ) {
this . lastUrl = '' ;
delete this . noWebPage ;
this . willSendWebPage = null ;
this . clearInput ( ) ;
}
if ( clearReply || clearInput ) {
this . clearHelper ( ) ;
}
this . updateSendBtn ( ) ;
}
public sendMessage ( force = false ) {
2021-11-29 17:51:29 +04:00
const { editMsgId , chat } = this ;
if ( chat . type === 'scheduled' && ! force && ! editMsgId ) {
2021-08-03 04:44:13 +03:00
this . scheduleSending ( ) ;
return ;
}
2022-02-20 17:37:37 +02:00
const { peerId } = chat ;
const { noWebPage } = this ;
const sendingParams = this . chat . getMessageSendingParams ( ) ;
2021-11-29 17:51:29 +04:00
2021-08-03 04:44:13 +03:00
const { value , entities } = getRichValue ( this . messageInputField . input ) ;
//return;
2021-11-29 17:51:29 +04:00
if ( editMsgId ) {
2021-10-21 17:16:43 +04:00
const message = this . editMessage ;
2021-11-29 17:51:29 +04:00
if ( value . trim ( ) || message . media ) {
2022-06-17 20:01:43 +04:00
this . managers . appMessagesManager . editMessage ( message , value , {
2021-08-09 21:47:45 +03:00
entities ,
2021-11-29 17:51:29 +04:00
noWebPage : noWebPage
2021-08-09 21:47:45 +03:00
} ) ;
2021-10-22 22:31:54 +04:00
this . onMessageSent ( ) ;
2021-08-09 21:47:45 +03:00
} else {
2021-11-29 17:51:29 +04:00
new PopupDeleteMessages ( peerId , [ editMsgId ] , chat . type ) ;
2021-08-09 21:47:45 +03:00
return ;
}
2021-11-29 17:51:29 +04:00
} else if ( value . trim ( ) ) {
2022-06-17 20:01:43 +04:00
this . managers . appMessagesManager . sendText ( peerId , value , {
2021-08-03 04:44:13 +03:00
entities ,
2022-02-20 17:37:37 +02:00
. . . sendingParams ,
2021-11-29 17:51:29 +04:00
noWebPage : noWebPage ,
2021-08-09 21:47:45 +03:00
webPage : this.getWebPagePromise ? undefined : this . willSendWebPage ,
2021-08-03 04:44:13 +03:00
clearDraft : true
} ) ;
2021-10-22 22:31:54 +04:00
2022-06-26 18:10:42 +02:00
if ( this . chat . type === 'scheduled' ) {
this . onMessageSent ( true ) ;
} else {
this . onMessageSent ( false , false ) ;
}
2021-10-22 22:31:54 +04:00
// this.onMessageSent();
2021-08-03 04:44:13 +03:00
}
// * wait for sendText set messageId for invokeAfterMsg
2021-09-12 08:12:39 +03:00
if ( this . forwarding ) {
const forwarding = copy ( this . forwarding ) ;
2022-06-30 16:14:33 +02:00
// setTimeout(() => {
2021-09-12 08:12:39 +03:00
for ( const fromPeerId in forwarding ) {
2022-06-17 20:01:43 +04:00
this . managers . appMessagesManager . forwardMessages ( peerId , fromPeerId . toPeerId ( ) , forwarding [ fromPeerId ] , {
2022-02-20 17:37:37 +02:00
. . . sendingParams ,
2021-12-02 17:44:18 +04:00
dropAuthor : this.forwardElements && this . forwardElements . hideSender . checkboxField . checked ,
2021-11-30 20:12:53 +04:00
dropCaptions : this.isDroppingCaptions ( )
2021-09-12 08:12:39 +03:00
} ) ;
}
2021-10-25 20:10:22 +03:00
if ( ! value ) {
this . onMessageSent ( ) ;
}
2022-06-30 16:14:33 +02:00
// }, 0);
2021-08-03 04:44:13 +03:00
}
2021-10-22 22:31:54 +04:00
// this.onMessageSent();
2021-08-03 04:44:13 +03:00
}
2022-06-17 20:01:43 +04:00
public async sendMessageWithDocument ( document : MyDocument | string , force = false , clearDraft = false ) {
document = await this . managers . appDocsManager . getDoc ( document ) ;
2021-08-03 04:44:13 +03:00
const flag = document . type === 'sticker' ? 'send_stickers' : ( document . type === 'gif' ? 'send_gifs' : 'send_media' ) ;
2022-06-17 20:01:43 +04:00
if ( this . chat . peerId . isAnyChat ( ) && ! ( await this . chat . canSend ( flag ) ) ) {
2021-08-03 04:44:13 +03:00
toast ( POSTING_MEDIA_NOT_ALLOWED ) ;
return false ;
}
if ( this . chat . type === 'scheduled' && ! force ) {
this . scheduleSending ( ( ) = > this . sendMessageWithDocument ( document , true , clearDraft ) ) ;
return false ;
}
if ( document ) {
2022-06-17 20:01:43 +04:00
this . managers . appMessagesManager . sendFile ( this . chat . peerId , document , {
2022-02-20 17:37:37 +02:00
. . . this . chat . getMessageSendingParams ( ) ,
2021-08-03 04:44:13 +03:00
isMedia : true ,
clearDraft : clearDraft || undefined
} ) ;
this . onMessageSent ( clearDraft , true ) ;
if ( document . type === 'sticker' ) {
emoticonsDropdown . stickersTab ? . pushRecentSticker ( document ) ;
}
return true ;
}
return false ;
}
2021-11-29 17:51:29 +04:00
private canToggleHideAuthor() {
2021-12-02 17:44:18 +04:00
const { forwardElements } = this ;
if ( ! forwardElements ) return false ;
const hideCaptionCheckboxField = forwardElements . hideCaption . checkboxField ;
2021-11-29 17:51:29 +04:00
return ! hideCaptionCheckboxField . checked ||
findUpTag ( hideCaptionCheckboxField . label , 'FORM' ) . classList . contains ( 'hide' ) ;
}
2021-11-30 20:12:53 +04:00
private isDroppingCaptions() {
return ! this . canToggleHideAuthor ( ) ;
}
2021-08-03 04:44:13 +03:00
/ * p u b l i c s e n d S o m e t h i n g ( c a l l b a c k : ( ) = > v o i d , f o r c e = f a l s e ) {
if ( this . chat . type === 'scheduled' && ! force ) {
this . scheduleSending ( ( ) = > this . sendSomething ( callback , true ) ) ;
return false ;
}
callback ( ) ;
this . onMessageSent ( false , true ) ;
return true ;
} * /
2022-06-17 20:01:43 +04:00
public async initMessageEditing ( mid : number ) {
const message = ( await this . chat . getMessage ( mid ) ) as Message . message ;
2021-08-03 04:44:13 +03:00
2022-04-25 17:54:30 +03:00
let input = documentFragmentToHTML ( wrapDraftText ( message . message , { entities : message.totalEntities } ) ) ;
2022-06-17 20:01:43 +04:00
const f = async ( ) = > {
const replyFragment = await wrapMessageForReply ( message , undefined , [ message . mid ] ) ;
2021-08-09 21:47:45 +03:00
this . setTopInfo ( 'edit' , f , i18n ( 'AccDescrEditing' ) , replyFragment , input , message ) ;
2021-08-03 04:44:13 +03:00
this . editMsgId = mid ;
2021-10-21 17:16:43 +04:00
this . editMessage = message ;
2021-08-03 04:44:13 +03:00
input = undefined ;
} ;
f ( ) ;
}
2021-10-21 17:16:43 +04:00
public initMessagesForward ( fromPeerIdsMids : { [ fromPeerId : PeerId ] : number [ ] } ) {
2022-06-17 20:01:43 +04:00
const f = async ( ) = > {
2021-08-03 04:44:13 +03:00
//const peerTitles: string[]
2022-06-17 20:01:43 +04:00
const fromPeerIds = Object . keys ( fromPeerIdsMids ) . map ( ( fromPeerId ) = > fromPeerId . toPeerId ( ) ) ;
2021-10-21 17:16:43 +04:00
const smth : Set < string > = new Set ( ) ;
2021-11-29 17:51:29 +04:00
let length = 0 , messagesWithCaptionsLength = 0 ;
2021-09-12 08:12:39 +03:00
2022-06-17 20:01:43 +04:00
const p = fromPeerIds . map ( async ( fromPeerId ) = > {
2021-09-12 08:12:39 +03:00
const mids = fromPeerIdsMids [ fromPeerId ] ;
2022-06-17 20:01:43 +04:00
const promises = mids . map ( async ( mid ) = > {
const message = ( await this . managers . appMessagesManager . getMessageByPeer ( fromPeerId , mid ) ) as Message . message ;
2021-09-12 08:12:39 +03:00
if ( message . fwd_from ? . from_name && ! message . fromId && ! message . fwdFromId ) {
2021-10-21 17:16:43 +04:00
smth . add ( 'N' + message . fwd_from . from_name ) ;
2021-09-12 08:12:39 +03:00
} else {
2021-10-21 17:16:43 +04:00
smth . add ( 'P' + message . fromId ) ;
2021-09-12 08:12:39 +03:00
}
2021-11-29 17:51:29 +04:00
if ( message . media && message . message ) {
++ messagesWithCaptionsLength ;
}
2021-09-12 08:12:39 +03:00
} ) ;
2022-06-17 20:01:43 +04:00
await Promise . all ( promises ) ;
2021-09-12 08:12:39 +03:00
length += mids . length ;
} ) ;
2021-08-03 04:44:13 +03:00
2022-06-17 20:01:43 +04:00
await Promise . all ( p ) ;
2021-08-09 21:47:45 +03:00
const onlyFirstName = smth . size > 2 ;
2022-06-17 20:01:43 +04:00
const peerTitles = [ . . . smth ] . map ( ( smth ) = > {
2021-10-21 17:16:43 +04:00
const type = smth [ 0 ] ;
smth = smth . slice ( 1 ) ;
2021-11-29 17:51:29 +04:00
if ( type === 'P' ) {
const peerId = smth . toPeerId ( ) ;
return peerId === rootScope . myId ? i18n ( 'Chat.Accessory.Forward.You' ) : new PeerTitle ( { peerId , dialog : false , onlyFirstName } ) . element ;
} else {
return onlyFirstName ? smth . split ( ' ' ) [ 0 ] : smth ;
}
2021-08-03 04:44:13 +03:00
} ) ;
2021-12-02 17:44:18 +04:00
const { forwardElements } = this ;
const form = findUpTag ( forwardElements . showCaption . checkboxField . label , 'FORM' ) ;
2021-11-30 20:12:53 +04:00
form . classList . toggle ( 'hide' , ! messagesWithCaptionsLength ) ;
2021-12-02 17:44:18 +04:00
const hideCaption = forwardElements . hideCaption . checkboxField . checked ;
2021-11-30 20:12:53 +04:00
if ( messagesWithCaptionsLength && hideCaption ) {
2021-12-02 17:44:18 +04:00
forwardElements . hideSender . checkboxField . setValueSilently ( true ) ;
2021-11-30 20:12:53 +04:00
} else if ( this . forwardWasDroppingAuthor !== undefined ) {
2021-12-02 17:44:18 +04:00
( this . forwardWasDroppingAuthor ? forwardElements.hideSender : forwardElements.showSender ) . checkboxField . setValueSilently ( true ) ;
2021-11-30 20:12:53 +04:00
}
2021-12-02 17:44:18 +04:00
const titleKey : LangPackKey = forwardElements . showSender . checkboxField . checked ? 'Chat.Accessory.Forward' : 'Chat.Accessory.Hidden' ;
2021-11-29 17:51:29 +04:00
const title = i18n ( titleKey , [ length ] ) ;
const senderTitles = document . createDocumentFragment ( ) ;
2021-08-09 21:47:45 +03:00
if ( peerTitles . length < 3 ) {
2021-11-29 17:51:29 +04:00
senderTitles . append ( . . . join ( peerTitles , false ) ) ;
2021-08-09 21:47:45 +03:00
} else {
2021-11-29 17:51:29 +04:00
senderTitles . append ( peerTitles [ 0 ] , i18n ( 'AndOther' , [ peerTitles . length - 1 ] ) ) ;
2021-08-09 21:47:45 +03:00
}
2021-11-29 17:51:29 +04:00
let firstMessage : Message.message , usingFullAlbum : boolean ;
2021-09-12 08:12:39 +03:00
if ( fromPeerIds . length === 1 ) {
const fromPeerId = fromPeerIds [ 0 ] ;
const mids = fromPeerIdsMids [ fromPeerId ] ;
2022-06-17 20:01:43 +04:00
firstMessage = ( await this . managers . appMessagesManager . getMessageByPeer ( fromPeerId , mids [ 0 ] ) ) as Message . message ;
2021-09-12 08:12:39 +03:00
usingFullAlbum = ! ! firstMessage . grouped_id ;
if ( usingFullAlbum ) {
2022-06-17 20:01:43 +04:00
const albumMids = await this . managers . appMessagesManager . getMidsByMessage ( firstMessage ) ;
if ( albumMids . length !== length || albumMids . find ( ( mid ) = > ! mids . includes ( mid ) ) ) {
2021-09-12 08:12:39 +03:00
usingFullAlbum = false ;
}
2021-08-03 04:44:13 +03:00
}
}
2021-11-29 17:51:29 +04:00
const subtitleFragment = document . createDocumentFragment ( ) ;
const delimiter = ': ' ;
2021-09-12 08:12:39 +03:00
if ( usingFullAlbum || length === 1 ) {
const mids = fromPeerIdsMids [ fromPeerIds [ 0 ] ] ;
2022-06-17 20:01:43 +04:00
const replyFragment = await wrapMessageForReply ( firstMessage , undefined , mids ) ;
2021-11-29 17:51:29 +04:00
subtitleFragment . append (
senderTitles ,
delimiter ,
replyFragment
) ;
2021-08-03 04:44:13 +03:00
} else {
2021-11-29 17:51:29 +04:00
subtitleFragment . append (
i18n ( 'Chat.Accessory.Forward.From' ) ,
delimiter ,
senderTitles
) ;
}
let newReply = this . setTopInfo ( 'forward' , f , title , subtitleFragment ) ;
2021-12-02 17:44:18 +04:00
forwardElements . modifyArgs . forEach ( ( b , idx ) = > {
2021-11-29 17:51:29 +04:00
const text = b . textElement ;
const intl : I18n.IntlElement = I18n . weakMap . get ( text ) as any ;
intl . args = [ idx < 2 ? fromPeerIds.length : messagesWithCaptionsLength ] ;
intl . update ( ) ;
} ) ;
if ( this . forwardHover ) {
this . forwardHover . attachButtonListener ( newReply , this . listenerSetter ) ;
2021-08-03 04:44:13 +03:00
}
2021-09-12 08:12:39 +03:00
this . forwarding = fromPeerIdsMids ;
2021-08-03 04:44:13 +03:00
} ;
f ( ) ;
}
2022-06-17 20:01:43 +04:00
public async initMessageReply ( mid : number ) {
2021-10-21 17:16:43 +04:00
if ( this . replyToMsgId === mid ) {
return ;
}
2022-06-17 20:01:43 +04:00
let message = await this . chat . getMessage ( mid ) ;
2021-08-03 04:44:13 +03:00
const f = ( ) = > {
2021-08-12 09:06:10 +03:00
let peerTitleEl : HTMLElement ;
2022-06-17 20:01:43 +04:00
if ( ! message ) { // load missing replying message
2021-08-12 09:06:10 +03:00
peerTitleEl = i18n ( 'Loading' ) ;
2022-06-17 20:01:43 +04:00
this . managers . appMessagesManager . wrapSingleMessage ( this . chat . peerId , mid ) . then ( ( _message ) = > {
2021-08-12 09:06:10 +03:00
if ( this . replyToMsgId !== mid ) {
return ;
}
2021-10-22 22:31:54 +04:00
message = _message ;
2022-06-17 20:01:43 +04:00
if ( ! message ) {
2021-08-12 09:06:10 +03:00
this . clearHelper ( 'reply' ) ;
} else {
f ( ) ;
}
} ) ;
} else {
peerTitleEl = new PeerTitle ( {
peerId : message.fromId ,
dialog : false
} ) . element ;
}
this . setTopInfo ( 'reply' , f , peerTitleEl , message && ( message as Message . message ) . message , undefined , message ) ;
2021-08-03 04:44:13 +03:00
this . replyToMsgId = mid ;
} ;
f ( ) ;
}
public clearHelper ( type ? : ChatInputHelperType ) {
if ( this . helperType === 'edit' && type !== 'edit' ) {
this . clearInput ( ) ;
}
if ( type ) {
this . lastUrl = '' ;
delete this . noWebPage ;
this . willSendWebPage = null ;
}
2021-10-21 17:16:43 +04:00
if ( type !== 'reply' ) {
this . replyToMsgId = undefined ;
this . forwarding = undefined ;
}
this . editMsgId = this . editMessage = undefined ;
2021-08-03 04:44:13 +03:00
this . helperType = this . helperFunc = undefined ;
if ( this . chat . container . classList . contains ( 'is-helper-active' ) ) {
appNavigationController . removeByType ( 'input-helper' ) ;
this . chat . container . classList . remove ( 'is-helper-active' ) ;
2021-10-28 04:01:53 +03:00
this . t ( ) ;
2021-08-03 04:44:13 +03:00
}
}
2021-10-28 04:01:53 +03:00
private t() {
const className = 'is-toggling-helper' ;
SetTransition ( this . chat . container , className , true , 150 , ( ) = > {
this . chat . container . classList . remove ( className ) ;
} ) ;
}
2021-08-03 04:44:13 +03:00
public setInputValue ( value : string , clear = true , focus = true ) {
if ( ! value ) value = '' ;
if ( clear ) this . clearInput ( false , false , value ) ;
else this . messageInputField . setValueSilently ( value ) ;
2021-08-09 21:47:45 +03:00
fastRaf ( ( ) = > {
2021-08-03 04:44:13 +03:00
focus && placeCaretAtEnd ( this . messageInput ) ;
this . onMessageInput ( ) ;
this . messageInput . scrollTop = this . messageInput . scrollHeight ;
} ) ;
}
2021-10-21 17:16:43 +04:00
public setTopInfo (
type : ChatInputHelperType ,
2021-08-09 17:34:08 +03:00
callerFunc : ( ) = > void ,
2021-08-09 21:47:45 +03:00
title : Parameters < typeof wrapReply > [ 0 ] = '' ,
subtitle : Parameters < typeof wrapReply > [ 1 ] = '' ,
2021-08-09 17:34:08 +03:00
input? : string ,
2021-10-21 17:16:43 +04:00
message? : any
) {
if ( this . willSendWebPage && type === 'reply' ) {
return ;
}
2021-08-03 04:44:13 +03:00
if ( type !== 'webpage' ) {
this . clearHelper ( type ) ;
this . helperType = type ;
this . helperFunc = callerFunc ;
}
2021-10-25 20:10:22 +03:00
2021-08-09 17:34:08 +03:00
const replyParent = this . replyElements . container ;
2021-10-25 20:10:22 +03:00
const oldReply = replyParent . lastElementChild . previousElementSibling ;
const haveReply = oldReply . classList . contains ( 'reply' ) ;
2021-08-03 04:44:13 +03:00
2021-10-21 17:16:43 +04:00
this . replyElements . iconBtn . replaceWith ( this . replyElements . iconBtn = ButtonIcon ( ( type === 'webpage' ? 'link' : type ) + ' active reply-icon' , { noRipple : true } ) ) ;
2022-06-17 20:01:43 +04:00
const { container } = wrapReply ( title , subtitle , message ) ;
2021-10-25 20:10:22 +03:00
if ( haveReply ) {
2022-06-17 20:01:43 +04:00
oldReply . replaceWith ( container ) ;
2021-10-25 20:10:22 +03:00
} else {
2022-06-17 20:01:43 +04:00
replyParent . insertBefore ( container , replyParent . lastElementChild ) ;
2021-10-25 20:10:22 +03:00
}
2021-08-09 17:34:08 +03:00
2022-01-13 22:50:18 +04:00
if ( type === 'webpage' ) {
2022-06-17 20:01:43 +04:00
container . style . cursor = 'default' ;
2022-01-13 22:50:18 +04:00
}
2021-10-28 04:01:53 +03:00
if ( ! this . chat . container . classList . contains ( 'is-helper-active' ) ) {
this . chat . container . classList . add ( 'is-helper-active' ) ;
this . t ( ) ;
}
2021-08-03 04:44:13 +03:00
/ * c o n s t s c r o l l = a p p I m M a n a g e r . s c r o l l a b l e ;
if ( scroll . isScrolledDown && ! scroll . scrollLocked && ! appImManager . messagesQueuePromise && ! appImManager . setPeerPromise ) {
scroll . scrollTo ( scroll . scrollHeight , 'top' , true , true , 200 ) ;
} * /
2021-09-26 17:59:10 +04:00
if ( ! IS_MOBILE ) {
2021-08-03 04:44:13 +03:00
appNavigationController . pushItem ( {
type : 'input-helper' ,
onPop : ( ) = > {
this . onHelperCancel ( ) ;
}
} ) ;
}
if ( input !== undefined ) {
this . setInputValue ( input ) ;
}
setTimeout ( ( ) = > {
this . updateSendBtn ( ) ;
} , 0 ) ;
2021-11-29 17:51:29 +04:00
2022-06-17 20:01:43 +04:00
return container ;
2021-08-03 04:44:13 +03:00
}
// public saveScroll() {
// this.scrollTop = this.chat.bubbles.scrollable.container.scrollTop;
// this.scrollOffsetTop = this.chatInput.offsetTop;
// }
// public restoreScroll() {
// if(this.chatInput.style.display) return;
// //console.log('input resize', offsetTop, this.chatInput.offsetTop);
// let newOffsetTop = this.chatInput.offsetTop;
// let container = this.chat.bubbles.scrollable.container;
// let scrollTop = container.scrollTop;
// let clientHeight = container.clientHeight;
// let maxScrollTop = container.scrollHeight;
// if(newOffsetTop < this.scrollOffsetTop) {
// this.scrollDiff = this.scrollOffsetTop - newOffsetTop;
// container.scrollTop += this.scrollDiff;
// } else if(scrollTop !== this.scrollTop) {
// let endDiff = maxScrollTop - (scrollTop + clientHeight);
// if(endDiff < this.scrollDiff/* && false */) {
// //container.scrollTop -= endDiff;
// } else {
// container.scrollTop -= this.scrollDiff;
// }
// }
// }
}