| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237 |
- /*
- * https://github.com/morethanwords/tweb
- * Copyright (C) 2019-2021 Eduard Kuzmenko
- * https://github.com/morethanwords/tweb/blob/master/LICENSE
- */
- import type {MyDocument} from '../../lib/appManagers/appDocsManager';
- import type {MyDraftMessage} from '../../lib/appManagers/appDraftsManager';
- import type {AppMessagesManager, MessageSendingParams} from '../../lib/appManagers/appMessagesManager';
- import type Chat from './chat';
- import {AppImManager, APP_TABS} from '../../lib/appManagers/appImManager';
- import '../../../public/recorder.min';
- import IS_TOUCH_SUPPORTED from '../../environment/touchSupport';
- import opusDecodeController from '../../lib/opusDecodeController';
- import {ButtonMenuItemOptions, ButtonMenuItemOptionsVerifiable, ButtonMenuSync} from '../buttonMenu';
- import emoticonsDropdown, {EmoticonsDropdown} from '../emoticonsDropdown';
- import PopupCreatePoll from '../popups/createPoll';
- import PopupForward from '../popups/forward';
- import PopupNewMedia, {getCurrentNewMediaPopup} from '../popups/newMedia';
- import {toast, toastNew} from '../toast';
- import {MessageEntity, DraftMessage, WebPage, Message, UserFull, AttachMenuPeerType, BotMenuButton, MessageMedia, InputReplyTo, Chat as MTChat, User, ChatFull} from '../../layer';
- import StickersHelper from './stickersHelper';
- import ButtonIcon from '../buttonIcon';
- import ButtonMenuToggle from '../buttonMenuToggle';
- import ListenerSetter, {Listener} from '../../helpers/listenerSetter';
- import Button, {replaceButtonIcon} from '../button';
- import PopupSchedule from '../popups/schedule';
- import SendMenu from './sendContextMenu';
- import rootScope from '../../lib/rootScope';
- import PopupPinMessage from '../popups/unpinMessage';
- import tsNow from '../../helpers/tsNow';
- import appNavigationController, {NavigationItem} from '../appNavigationController';
- import {IS_MOBILE, IS_MOBILE_SAFARI} from '../../environment/userAgent';
- import I18n, {FormatterArguments, i18n, join, LangPackKey} from '../../lib/langPack';
- import {chatHistoryService} from '../../lib/api/chatHistoryService';
- import {chatRecordsService} from '../../lib/api/chatRecordsService';
- import {messagesService} from '../../lib/api/messagesService';
- import {generateTail} from './bubbles';
- import findUpClassName from '../../helpers/dom/findUpClassName';
- import ButtonCorner from '../buttonCorner';
- import blurActiveElement from '../../helpers/dom/blurActiveElement';
- import cancelEvent from '../../helpers/dom/cancelEvent';
- import cancelSelection from '../../helpers/dom/cancelSelection';
- import {attachClickEvent, simulateClickEvent} from '../../helpers/dom/clickEvent';
- import isInputEmpty from '../../helpers/dom/isInputEmpty';
- import isSendShortcutPressed from '../../helpers/dom/isSendShortcutPressed';
- import placeCaretAtEnd from '../../helpers/dom/placeCaretAtEnd';
- 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 {putPreloader} from '../putPreloader';
- import SetTransition from '../singleTransition';
- import PeerTitle from '../peerTitle';
- import {fastRaf} from '../../helpers/schedulers';
- import PopupDeleteMessages from '../popups/deleteMessages';
- import fixSafariStickyInputFocusing, {IS_STICKY_INPUT_BUGGED} from '../../helpers/dom/fixSafariStickyInputFocusing';
- import PopupPeer from '../popups/peer';
- import appMediaPlaybackController from '../appMediaPlaybackController';
- import {BOT_START_PARAM, GENERAL_TOPIC_ID, NULL_PEER_ID, SEND_PAID_WITH_STARS_DELAY, SEND_WHEN_ONLINE_TIMESTAMP} from '../../lib/mtproto/mtproto_config';
- import setCaretAt from '../../helpers/dom/setCaretAt';
- import DropdownHover from '../../helpers/dropdownHover';
- import findUpTag from '../../helpers/dom/findUpTag';
- import toggleDisability from '../../helpers/dom/toggleDisability';
- import callbackify from '../../helpers/callbackify';
- import ChatBotCommands from './botCommands';
- import copy from '../../helpers/object/copy';
- import toHHMMSS from '../../helpers/string/toHHMMSS';
- import documentFragmentToHTML from '../../helpers/dom/documentFragmentToHTML';
- 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';
- 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';
- import filterAsync from '../../helpers/array/filterAsync';
- import InputFieldAnimated from '../inputFieldAnimated';
- import getStickerEffectThumb from '../../lib/appManagers/utils/stickers/getStickerEffectThumb';
- import PopupStickers from '../popups/stickers';
- import wrapPeerTitle from '../wrappers/peerTitle';
- import wrapReply from '../wrappers/reply';
- import {getEmojiFromElement} from '../emoticonsDropdown/tabs/emoji';
- import RichInputHandler from '../../helpers/dom/richInputHandler';
- import {insertRichTextAsHTML} from '../inputField';
- import draftsAreEqual from '../../lib/appManagers/utils/drafts/draftsAreEqual';
- import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText';
- import getAttachMenuBotIcon from '../../lib/appManagers/utils/attachMenuBots/getAttachMenuBotIcon';
- import forEachReverse from '../../helpers/array/forEachReverse';
- import {MARKDOWN_ENTITIES} from '../../lib/richTextProcessor';
- import IMAGE_MIME_TYPES_SUPPORTED from '../../environment/imageMimeTypesSupport';
- import VIDEO_MIME_TYPES_SUPPORTED from '../../environment/videoMimeTypesSupport';
- import {ChatRights} from '../../lib/appManagers/appChatsManager';
- import getPeerActiveUsernames from '../../lib/appManagers/utils/peers/getPeerActiveUsernames';
- import replaceContent from '../../helpers/dom/replaceContent';
- import getTextWidth from '../../helpers/canvas/getTextWidth';
- import {FontFull} from '../../config/font';
- import {ChatType} from './chat';
- import deferredPromise, {CancellablePromise} from '../../helpers/cancellablePromise';
- import idleController from '../../helpers/idleController';
- import Icon from '../icon';
- import setBadgeContent from '../../helpers/setBadgeContent';
- import createBadge from '../../helpers/createBadge';
- import deepEqual from '../../helpers/object/deepEqual';
- import {clearMarkdownExecutions, createMarkdownCache, handleMarkdownShortcut, maybeClearUndoHistory, processCurrentFormatting} from '../../helpers/dom/markdown';
- import MarkupTooltip from './markupTooltip';
- import PopupPremium from '../popups/premium';
- import PopupPickUser from '../popups/pickUser';
- import getPeerId from '../../lib/appManagers/utils/peers/getPeerId';
- import {isSavedDialog} from '../../lib/appManagers/utils/dialogs/isDialog';
- import getFwdFromName from '../../lib/appManagers/utils/messages/getFwdFromName';
- import apiManagerProxy from '../../lib/mtproto/mtprotoworker';
- import eachSecond from '../../helpers/eachSecond';
- import {wrapSlowModeLeftDuration} from '../wrappers/wrapDuration';
- import showTooltip from '../tooltip';
- import createContextMenu from '../../helpers/dom/createContextMenu';
- import {Accessor, createEffect, createMemo, createRoot, createSignal, Setter} from 'solid-js';
- import {createStore} from 'solid-js/store';
- import SelectedEffect from './selectedEffect';
- import windowSize from '../../helpers/windowSize';
- import {numberThousandSplitterForStars} from '../../helpers/number/numberThousandSplitter';
- import accumulate from '../../helpers/array/accumulate';
- import splitStringByLength from '../../helpers/string/splitStringByLength';
- import PaidMessagesInterceptor, {PAYMENT_REJECTED} from './paidMessagesInterceptor';
- import asyncThrottle from '../../helpers/schedulers/asyncThrottle';
- import focusInput from '../../helpers/dom/focusInput';
- import {PopupChecklist} from '../popups/checklist';
- // console.log('Recorder', Recorder);
- const RECORD_MIN_TIME = 500;
- const REPLY_IN_TOPIC = false;
- export const POSTING_NOT_ALLOWED_MAP: {[action in ChatRights]?: LangPackKey} = {
- send_voices: 'GlobalAttachVoiceRestricted',
- send_stickers: 'GlobalAttachStickersRestricted',
- send_gifs: 'GlobalAttachGifRestricted',
- send_media: 'GlobalAttachMediaRestricted',
- send_plain: 'GlobalSendMessageRestricted',
- send_polls: 'ErrorSendRestrictedPollsAll',
- send_inline: 'GlobalAttachInlineRestricted'
- };
- type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply';
- type ChatSendBtnIcon = 'send' | 'record' | 'edit' | 'schedule' | 'forward';
- export type ChatInputReplyTo = Pick<MessageSendingParams, 'replyToMsgId' | 'replyToQuote' | 'replyToStoryId' | 'replyToPeerId'>;
- const CLASS_NAME = 'chat-input';
- const PEER_EXCEPTIONS = new Set<ChatType>([ChatType.Scheduled, ChatType.Stories, ChatType.Saved]);
- 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: InputFieldAnimated;
- private fileInput: HTMLInputElement;
- private inputMessageContainer: HTMLDivElement;
- private btnSend: HTMLButtonElement;
- public btnCancelRecord: HTMLButtonElement;
- public btnReaction: HTMLButtonElement;
- public lastUrl = '';
- private lastTimeType = 0;
- public noRipple: boolean;
- public chatInput: HTMLElement;
- public inputContainer: HTMLElement;
- public rowsWrapper: HTMLDivElement;
- private newMessageWrapper: HTMLDivElement;
- private btnToggleEmoticons: HTMLButtonElement;
- private btnToggleReplyMarkup: HTMLButtonElement;
- public btnSendContainer: HTMLDivElement;
- private replyKeyboard: ReplyKeyboard;
- public attachMenu: HTMLElement;
- private attachMenuButtons: ButtonMenuItemOptionsVerifiable[];
- private sendMenu: SendMenu;
- private replyElements: {
- container: HTMLElement,
- cancelBtn: HTMLButtonElement,
- iconBtn: HTMLButtonElement,
- menuContainer: HTMLElement,
- replyInAnother: ButtonMenuItemOptions,
- doNotReply: ButtonMenuItemOptions,
- doNotQuote: ButtonMenuItemOptions
- } = {} as any;
- private forwardElements: {
- changePeer: ButtonMenuItemOptions,
- showSender: ButtonMenuItemOptions,
- hideSender: ButtonMenuItemOptions,
- showCaption: ButtonMenuItemOptions,
- hideCaption: ButtonMenuItemOptions,
- container: HTMLElement,
- modifyArgs?: ButtonMenuItemOptions[]
- };
- private webPageElements: {
- above: ButtonMenuItemOptions,
- below: ButtonMenuItemOptions,
- larger: ButtonMenuItemOptions,
- smaller: ButtonMenuItemOptions,
- container: HTMLElement
- };
- private forwardHover: DropdownHover;
- private webPageHover: DropdownHover;
- private replyHover: DropdownHover;
- private currentHover: DropdownHover;
- private forwardWasDroppingAuthor: boolean;
- private getWebPagePromise: Promise<void>;
- public willSendWebPage: WebPage = null;
- public webPageOptions: Parameters<AppMessagesManager['sendText']>[0]['webPageOptions'] = {};
- private forwarding: {[fromPeerId: PeerId]: number[]};
- public replyToMsgId: MessageSendingParams['replyToMsgId'];
- public replyToStoryId: MessageSendingParams['replyToStoryId'];
- public replyToQuote: MessageSendingParams['replyToQuote'];
- public replyToPeerId: MessageSendingParams['replyToPeerId'];
- public editMsgId: number;
- public editMessage: Message.message;
- private noWebPage: true;
- public scheduleDate: number;
- public sendSilent: true;
- public startParam: string;
- public invertMedia: boolean;
- public effect: Accessor<DocId>;
- public setEffect: Setter<DocId>;
- private recorder: any;
- public recording = false;
- private recordCanceled = false;
- private recordTimeEl: HTMLElement;
- private recordRippleEl: HTMLElement;
- private recordStartTime = 0;
- private recordingOverlayListener: Listener;
- private recordingNavigationItem: NavigationItem;
- // private scrollTop = 0;
- // private scrollOffsetTop = 0;
- // private scrollDiff = 0;
- public helperType: Exclude<ChatInputHelperType, 'webpage'>;
- private helperFunc: () => void | Promise<void>;
- private helperWaitingForward: boolean;
- private helperWaitingReply: boolean;
- public willAttachType: 'document' | 'media';
- private autocompleteHelperController: AutocompleteHelperController;
- private stickersHelper: StickersHelper;
- private emojiHelper: EmojiHelper;
- private commandsHelper: CommandsHelper;
- private mentionsHelper: MentionsHelper;
- private inlineHelper: InlineHelper;
- private listenerSetter: ListenerSetter;
- private hoverListenerSetter: ListenerSetter;
- private pinnedControlBtn: HTMLButtonElement;
- private openChatBtn: HTMLButtonElement;
- private goDownBtn: HTMLButtonElement;
- private goDownUnreadBadge: HTMLElement;
- private goMentionBtn: HTMLButtonElement;
- private goMentionUnreadBadge: HTMLSpanElement;
- private goReactionBtn: HTMLButtonElement;
- private goReactionUnreadBadge: HTMLElement;
- private btnScheduled: HTMLButtonElement;
- private btnPreloader: HTMLButtonElement;
- private saveDraftDebounced: () => void;
- private fakeRowsWrapper: HTMLDivElement;
- private previousQuery: string;
- private releaseMediaPlayback: () => void;
- private botStartBtn: HTMLButtonElement;
- private unblockBtn: HTMLButtonElement;
- private onlyPremiumBtn: HTMLButtonElement;
- private onlyPremiumBtnText: I18n.IntlElement;
- private joinBtn: HTMLButtonElement;
- private rowsWrapperWrapper: HTMLDivElement;
- private controlContainer: HTMLElement;
- private fakeSelectionWrapper: HTMLDivElement;
- private starsBadge: HTMLElement;
- private starsBadgeStars: HTMLElement;
- private fakeWrapperTo: HTMLElement;
- private toggleControlButtonDisability: () => void;
- private botCommandsToggle: HTMLElement;
- private botCommands: ChatBotCommands;
- private botCommandsIcon: HTMLElement;
- private botCommandsView: HTMLElement;
- private botMenuButton: BotMenuButton.botMenuButton;
- private hasBotCommands: boolean;
- // private activeContainer: HTMLElement;
- private sendAs: ChatSendAs;
- public sendAsPeerId: PeerId;
- private replyInTopicOverlay: HTMLDivElement;
- private restoreInputLock: () => void;
- private isFocused: boolean;
- private freezedFocused: boolean;
- public onFocusChange: (isFocused: boolean) => void;
- public onMenuToggle: (isOpen: boolean) => void;
- public onRecording: (isRecording: boolean) => void;
- public onUpdateSendBtn: (icon: ChatSendBtnIcon) => void;
- public onMessageSent2: () => void;
- public forwardStoryCallback: (e: MouseEvent) => void;
- public emoticonsDropdown: EmoticonsDropdown;
- public excludeParts: Partial<{
- scheduled: boolean,
- replyMarkup: boolean,
- downButton: boolean,
- reply: boolean,
- forwardOptions: boolean,
- mentionButton: boolean,
- botCommands: boolean,
- attachMenu: boolean,
- commandsHelper: boolean,
- emoticons: boolean
- }>;
- public globalMentions: boolean;
- public onFileSelection: (promise: Promise<File[]>) => void;
- private hasOffset: {type: 'commands' | 'as', forwards: boolean};
- private canForwardStory: boolean;
- private processingDraftMessage: DraftMessage.draftMessage;
- private fileSelectionPromise: CancellablePromise<File[]>;
- public paidMessageInterceptor: PaidMessagesInterceptor;
- private starsState: ReturnType<ChatInput['constructStarsState']>;
- constructor(
- public chat: Chat,
- private appImManager: AppImManager,
- private managers: AppManagers,
- private className: string
- ) {
- this.listenerSetter = new ListenerSetter();
- this.hoverListenerSetter = new ListenerSetter();
- this.excludeParts = {};
- this.isFocused = false;
- this.emoticonsDropdown = emoticonsDropdown;
- }
- public construct() {
- const className2 = this.className;
- this.chatInput = document.createElement('div');
- this.chatInput.classList.add(CLASS_NAME, className2, 'hide');
- this.inputContainer = document.createElement('div');
- this.inputContainer.classList.add(`${CLASS_NAME}-container`, `${className2}-container`);
- this.rowsWrapperWrapper = document.createElement('div');
- this.rowsWrapperWrapper.classList.add('rows-wrapper-wrapper');
- this.rowsWrapper = document.createElement('div');
- this.rowsWrapper.classList.add(...[
- 'rows-wrapper',
- `${CLASS_NAME}-wrapper`,
- `${className2}-wrapper`,
- this.chat.type !== ChatType.Stories && 'chat-rows-wrapper'
- ].filter(Boolean));
- this.rowsWrapperWrapper.append(this.rowsWrapper);
- const tail = generateTail(!this.chat.isMainChat);
- this.rowsWrapper.append(tail);
- const fakeRowsWrapper = this.fakeRowsWrapper = document.createElement('div');
- fakeRowsWrapper.classList.add('fake-wrapper', 'fake-rows-wrapper');
- const fakeSelectionWrapper = this.fakeSelectionWrapper = document.createElement('div');
- fakeSelectionWrapper.classList.add('fake-wrapper', 'fake-selection-wrapper');
- this.inputContainer.append(this.rowsWrapperWrapper, fakeRowsWrapper, fakeSelectionWrapper);
- this.chatInput.append(this.inputContainer);
- if(!this.excludeParts.downButton) {
- this.constructGoDownButton();
- }
- // * constructor end
- /* let setScrollTopTimeout: number;
- // @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
- /* this.listenerSetter.add(window.visualViewport)('resize', () => {
- 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);
- }); */
- const c = this.controlContainer = document.createElement('div');
- c.classList.add('chat-input-control', 'chat-input-wrapper');
- this.inputContainer.append(c);
- this.paidMessageInterceptor = new PaidMessagesInterceptor(this.chat, this.managers);
- this.getMiddleware().onDestroy(() => {
- this.paidMessageInterceptor.dispose();
- });
- this.starsState = this.constructStarsState();
- }
- public freezeFocused(focused: boolean) {
- if(this.freezedFocused === focused) {
- return;
- }
- this.freezedFocused = focused;
- this.updateSendBtn();
- }
- public createButtonIcon(...args: Parameters<typeof ButtonIcon>) {
- if(this.noRipple) {
- args[1] ??= {};
- args[1].noRipple = true;
- }
- const button = ButtonIcon(...args);
- button.tabIndex = -1;
- return button;
- }
- private constructGoDownButton() {
- this.goDownBtn = ButtonCorner({icon: 'arrow_down', className: 'bubbles-corner-button chat-secondary-button bubbles-go-down hide'});
- this.inputContainer.append(this.goDownBtn);
- attachClickEvent(this.goDownBtn, (e) => {
- cancelEvent(e);
- this.chat.bubbles.onGoDownClick();
- }, {listenerSetter: this.listenerSetter});
- }
- private constructReplyElements() {
- this.replyElements.container = document.createElement('div');
- this.replyElements.container.classList.add('reply-wrapper', 'rows-wrapper-row');
- this.replyElements.iconBtn = this.createButtonIcon('');
- this.replyElements.cancelBtn = this.createButtonIcon('close reply-cancel', {noRipple: true});
- this.replyElements.container.append(this.replyElements.iconBtn, this.replyElements.cancelBtn);
- attachClickEvent(this.replyElements.cancelBtn, this.onHelperCancel, {listenerSetter: this.listenerSetter});
- attachClickEvent(this.replyElements.container, this.onHelperClick, {listenerSetter: this.listenerSetter});
- const buttons: ButtonMenuItemOptions[] = [{
- icon: 'message_jump',
- text: 'ShowMessage',
- onClick: () => {
- this.onHelperClick();
- this.replyHover.toggle(false);
- }
- }, this.replyElements.replyInAnother = {
- icon: 'replace',
- text: 'ReplyToAnotherChat',
- onClick: () => this.changeReplyRecipient()
- }, this.replyElements.doNotReply = {
- icon: 'delete',
- text: 'DoNotReply',
- onClick: this.onHelperCancel,
- danger: true/* ,
- separator: true */
- }, this.replyElements.doNotQuote = {
- icon: 'delete',
- text: 'DoNotQuote',
- onClick: this.onHelperCancel,
- danger: true
- }];
- const btnMenu = this.replyElements.menuContainer = ButtonMenuSync({
- buttons,
- listenerSetter: this.listenerSetter
- });
- if(!IS_TOUCH_SUPPORTED) {
- this.replyHover = new DropdownHover({element: btnMenu});
- }
- this.replyElements.container.append(btnMenu);
- }
- private constructForwardElements() {
- const onHideAuthorClick = () => {
- isChangingAuthor = true;
- };
- 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,
- checkForClose: () => this.canToggleHideAuthor(),
- radioGroup: 'author'
- },
- forwardElements.hideSender = {
- text: 'Chat.Alert.Forward.Action.Hide1',
- onClick: onHideAuthorClick,
- checkForClose: () => this.canToggleHideAuthor(),
- radioGroup: 'author'
- },
- forwardElements.showCaption = {
- text: 'Chat.Alert.Forward.Action.ShowCaption',
- onClick: onHideCaptionClick,
- radioGroup: 'caption'
- },
- forwardElements.hideCaption = {
- text: 'Chat.Alert.Forward.Action.HideCaption',
- onClick: onHideCaptionClick,
- radioGroup: 'caption'
- },
- forwardElements.changePeer = {
- text: 'Chat.Alert.Forward.Action.Another',
- onClick: () => {
- this.changeForwardRecipient();
- },
- icon: 'replace'
- },
- {
- icon: 'delete',
- text: 'DoNotForward',
- onClick: this.onHelperCancel,
- danger: true
- }
- ];
- const forwardBtnMenu = forwardElements.container = ButtonMenuSync({
- buttons: forwardButtons,
- radioGroups: [{
- name: 'author',
- onChange: (value) => {
- 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();
- }
- },
- checked: 0
- }, {
- name: 'caption',
- 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;
- },
- checked: 0
- }],
- listenerSetter: this.listenerSetter
- });
- if(!IS_TOUCH_SUPPORTED) {
- this.forwardHover = new DropdownHover({element: forwardBtnMenu});
- }
- forwardElements.modifyArgs = forwardButtons.slice(0, -2);
- this.replyElements.container.append(forwardBtnMenu);
- }
- private constructWebPageElements() {
- this.webPageElements = {} as any;
- const buttons: ButtonMenuItemOptions[] = [this.webPageElements.above = {
- text: 'AboveMessage',
- onClick: () => {},
- radioGroup: 'position'
- }, this.webPageElements.below = {
- text: 'BelowMessage',
- onClick: () => {},
- radioGroup: 'position'
- }, this.webPageElements.larger = {
- text: 'LargerMedia',
- onClick: () => {},
- radioGroup: 'size'
- }, this.webPageElements.smaller = {
- text: 'SmallerMedia',
- onClick: () => {},
- radioGroup: 'size'
- }, {
- text: 'WebPage.RemovePreview',
- onClick: () => {
- this.onHelperCancel();
- },
- icon: 'delete',
- danger: true
- }];
- const btnMenu = this.webPageElements.container = ButtonMenuSync({
- buttons,
- radioGroups: [{
- name: 'position',
- onChange: (value) => {
- this.invertMedia = !!+value;
- this.saveDraftDebounced?.();
- },
- checked: 0
- }, {
- name: 'size',
- onChange: (value) => {
- this.webPageOptions.largeMedia = !!+value;
- this.webPageOptions.smallMedia = !+value;
- this.saveDraftDebounced?.();
- },
- checked: 0
- }],
- listenerSetter: this.listenerSetter
- });
- if(!IS_TOUCH_SUPPORTED) {
- this.webPageHover = new DropdownHover({element: btnMenu});
- }
- this.replyElements.container.append(btnMenu);
- }
- private constructMentionButton(isReaction?: boolean) {
- const btn = ButtonCorner({icon: isReaction ? 'reactions' : 'mention', className: 'bubbles-corner-button chat-secondary-button bubbles-go-mention bubbles-go-reaction'});
- const badge = createBadge('span', 24, 'primary');
- btn.append(badge);
- this.inputContainer.append(btn);
- attachClickEvent(btn, (e) => {
- cancelEvent(e);
- const middleware = this.getMiddleware();
- this.managers.appMessagesManager.goToNextMention({peerId: this.chat.peerId, threadId: this.chat.threadId, isReaction}).then((mid) => {
- if(!middleware()) {
- return;
- }
- if(mid) {
- this.chat.setMessageId({lastMsgId: mid});
- }
- });
- }, {listenerSetter: this.listenerSetter});
- createContextMenu({
- buttons: [{
- icon: 'readchats',
- text: isReaction ? 'ReadAllReactions' : 'ReadAllMentions',
- onClick: () => {
- this.managers.appMessagesManager.readMentions(this.chat.peerId, this.chat.threadId, isReaction);
- }
- }],
- listenTo: btn,
- listenerSetter: this.listenerSetter
- });
- if(isReaction) {
- this.goReactionUnreadBadge = badge;
- this.goReactionBtn = btn;
- } else {
- this.goMentionUnreadBadge = badge;
- this.goMentionBtn = btn;
- }
- }
- private constructScheduledButton() {
- this.btnScheduled = this.createButtonIcon('scheduled btn-scheduled float hide', {noRipple: true});
- attachClickEvent(this.btnScheduled, (e) => {
- this.appImManager.openScheduled(this.chat.peerId);
- }, {listenerSetter: this.listenerSetter});
- this.listenerSetter.add(rootScope)('scheduled_new', ({peerId}) => {
- if(this.chat.peerId !== peerId) {
- return;
- }
- this.btnScheduled.classList.remove('hide');
- });
- this.listenerSetter.add(rootScope)('scheduled_delete', ({peerId}) => {
- if(this.chat.peerId !== peerId) {
- return;
- }
- this.managers.appMessagesManager.getScheduledMessages(this.chat.peerId).then((value) => {
- this.btnScheduled.classList.toggle('hide', !value.length);
- });
- });
- }
- private constructReplyMarkup() {
- this.btnToggleReplyMarkup = this.createButtonIcon('botcom toggle-reply-markup float hide', {noRipple: true});
- this.replyKeyboard = new ReplyKeyboard({
- appendTo: this.rowsWrapper,
- listenerSetter: this.listenerSetter,
- managers: this.managers,
- btnHover: this.btnToggleReplyMarkup,
- chatInput: this
- });
- this.listenerSetter.add(this.replyKeyboard)('open', () => this.btnToggleReplyMarkup.classList.add('active'));
- this.listenerSetter.add(this.replyKeyboard)('close', () => this.btnToggleReplyMarkup.classList.remove('active'));
- }
- private constructBotCommands() {
- this.botCommands = new ChatBotCommands(this.rowsWrapper, this, this.managers);
- this.botCommandsToggle = document.createElement('div');
- this.botCommandsToggle.classList.add('new-message-bot-commands');
- this.botCommandsToggle.append(Icon('webview', 'new-message-bot-commands-view-icon'));
- 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.botCommandsView = document.createElement('div');
- this.botCommandsView.classList.add('new-message-bot-commands-view');
- this.botCommandsToggle.append(scaler, this.botCommandsView);
- let webViewTempId = 0, waitingForWebView = false;
- attachClickEvent(this.botCommandsToggle, (e) => {
- cancelEvent(e);
- const botId = this.chat.peerId.toUserId();
- const {botMenuButton} = this;
- if(botMenuButton) {
- if(waitingForWebView) {
- return;
- }
- const tempId = ++webViewTempId;
- waitingForWebView = true;
- Promise.resolve().then(() => {
- if(webViewTempId !== tempId) {
- return;
- }
- return this.chat.openWebApp({
- botId,
- url: botMenuButton.url,
- buttonText: botMenuButton.text,
- fromBotMenu: true
- });
- }).finally(() => {
- if(webViewTempId === tempId) {
- waitingForWebView = false;
- }
- });
- return;
- }
- const middleware = this.getMiddleware();
- const isShown = icon.classList.contains('state-back');
- if(isShown) {
- this.botCommands.toggle(true);
- // icon.classList.remove('state-back');
- } else {
- this.botCommands.setUserId(botId, middleware);
- // 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');
- });
- }
- private constructRecorder() {
- const Recorder = (window as any).Recorder;
- if(Recorder) 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);
- }
- if(!this.recorder) {
- return;
- }
- attachClickEvent(this.btnCancelRecord, this.onCancelRecordClick, {listenerSetter: this.listenerSetter});
- this.recorder.onstop = () => {
- this.setRecording(false);
- this.chatInput.classList.remove('is-locked');
- this.recordRippleEl.style.transform = '';
- };
- this.recorder.ondataavailable = async(typedArray: Uint8Array) => {
- if(this.releaseMediaPlayback) {
- this.releaseMediaPlayback();
- this.releaseMediaPlayback = undefined;
- }
- if(this.recordingOverlayListener) {
- this.listenerSetter.remove(this.recordingOverlayListener);
- this.recordingOverlayListener = undefined;
- }
- if(this.recordingNavigationItem) {
- appNavigationController.removeItem(this.recordingNavigationItem);
- this.recordingNavigationItem = undefined;
- }
- if(this.recordCanceled) {
- return;
- }
- const sendingParams = this.chat.getMessageSendingParams();
- const preparedPaymentResult = await this.paidMessageInterceptor.prepareStarsForPayment(1);
- if(preparedPaymentResult === PAYMENT_REJECTED) return;
- sendingParams.confirmedPaymentResult = preparedPaymentResult;
- const duration = (Date.now() - this.recordStartTime) / 1000 | 0;
- const dataBlob = new Blob([typedArray as unknown as ArrayBuffer], {type: 'audio/ogg'});
- opusDecodeController.decode(typedArray, true).then((result) => {
- opusDecodeController.setKeepAlive(false);
- // тут objectURL ставится уже с audio/wav
- this.managers.appMessagesManager.sendFile({
- ...sendingParams,
- file: dataBlob,
- isVoiceMessage: true,
- isMedia: true,
- duration,
- waveform: result.waveform,
- objectURL: result.url,
- clearDraft: true
- });
- this.onMessageSent(false, true);
- });
- };
- }
- public constructPeerHelpers() {
- if(!this.excludeParts.reply) {
- this.constructReplyElements();
- if(!this.excludeParts.forwardOptions) {
- this.constructForwardElements();
- this.constructWebPageElements();
- }
- }
- this.newMessageWrapper = document.createElement('div');
- this.newMessageWrapper.classList.add('new-message-wrapper', 'rows-wrapper-row');
- if(REPLY_IN_TOPIC) {
- this.replyInTopicOverlay = document.createElement('div');
- this.replyInTopicOverlay.classList.add('reply-in-topic-overlay', 'hide');
- this.replyInTopicOverlay.append(i18n('Chat.Input.ReplyToAnswer'));
- }
- if(!this.excludeParts.emoticons) this.btnToggleEmoticons = this.createButtonIcon('smile toggle-emoticons', {noRipple: true});
- this.inputMessageContainer = document.createElement('div');
- this.inputMessageContainer.classList.add('input-message-container');
- if(this.goDownBtn) {
- this.goDownUnreadBadge = createBadge('span', 24, 'primary');
- this.goDownBtn.append(this.goDownUnreadBadge);
- }
- if(!this.excludeParts.mentionButton) {
- this.constructMentionButton();
- this.constructMentionButton(true);
- }
- if(!this.excludeParts.scheduled) {
- this.constructScheduledButton();
- }
- if(!this.excludeParts.replyMarkup) {
- this.constructReplyMarkup();
- }
- if(!this.excludeParts.botCommands) {
- this.constructBotCommands();
- }
- // const getSendMediaRights = () => Promise.all([this.chat.canSend('send_photos'), this.chat.canSend('send_videos')]).then(([photos, videos]) => ({photos, videos}));
- this.attachMenuButtons = [{
- icon: 'image',
- text: 'Chat.Input.Attach.PhotoOrVideo',
- onClick: () => this.onAttachClick(false, true, true)
- // verify: () => getSendMediaRights().then(({photos, videos}) => photos && videos)
- }, /* {
- icon: 'image',
- text: 'AttachPhoto',
- onClick: () => onAttachMediaClick(true, false),
- verify: () => getSendMediaRights().then(({photos, videos}) => photos && !videos)
- }, {
- icon: 'image',
- text: 'AttachVideo',
- onClick: () => onAttachMediaClick(false, true),
- verify: () => getSendMediaRights().then(({photos, videos}) => !photos && videos)
- }, */ {
- icon: 'document',
- text: 'Chat.Input.Attach.Document',
- onClick: () => this.onAttachClick(true)
- // verify: () => this.chat.canSend('send_docs')
- }, {
- icon: 'gift',
- text: 'GiftPremium',
- onClick: () => this.chat.appImManager.giftPremium(this.chat.peerId),
- verify: () => {
- return this.chat && Promise.all([
- this.chat.canGiftPremium(),
- this.managers.apiManager.getAppConfig()
- ]).then(([canGift, {premium_gift_attach_menu_icon}]) => canGift && premium_gift_attach_menu_icon);
- }
- }, {
- icon: 'poll',
- text: 'Poll',
- onClick: async() => {
- const action: ChatRights = 'send_polls';
- if(!(await this.chat.canSend(action))) {
- toastNew({langPackKey: POSTING_NOT_ALLOWED_MAP[action]});
- return;
- }
- PopupElement.createPopup(PopupCreatePoll, this.chat).show();
- },
- verify: () => this.chat.peerId.isAnyChat() || this.chat.isBot
- }, {
- icon: 'poll',
- text: 'Checklist',
- onClick: async() => {
- if(this.chat.peerId.isAnyChat()) {
- const action: ChatRights = 'send_polls';
- if(!(await this.chat.canSend(action))) {
- toastNew({langPackKey: POSTING_NOT_ALLOWED_MAP[action]});
- return;
- }
- }
- if(!rootScope.premium) {
- PopupPremium.show();
- return;
- }
- PopupElement.createPopup(PopupChecklist, {chat: this.chat}).show();
- }
- }];
- const attachMenuButtons = this.attachMenuButtons.slice();
- this.attachMenu = ButtonMenuToggle({
- buttonOptions: {noRipple: true},
- listenerSetter: this.listenerSetter,
- direction: 'top-left',
- buttons: this.attachMenuButtons,
- onOpenBefore: this.excludeParts.attachMenu ? undefined : async() => {
- const attachMenuBots = await this.managers.appAttachMenuBotsManager.getAttachMenuBots();
- const buttons = attachMenuButtons.slice();
- const attachMenuBotsButtons = attachMenuBots.filter((attachMenuBot) => {
- return attachMenuBot.pFlags.show_in_attach_menu;
- }).map((attachMenuBot) => {
- const icon = getAttachMenuBotIcon(attachMenuBot);
- const button: typeof buttons[0] = {
- regularText: wrapEmojiText(attachMenuBot.short_name),
- onClick: () => {
- this.chat.openWebApp({attachMenuBot, fromAttachMenu: true});
- },
- iconDoc: icon?.icon as MyDocument,
- verify: async() => {
- let found = false;
- const verifyMap: {
- [type in AttachMenuPeerType['_']]: () => boolean | Promise<boolean>
- } = {
- attachMenuPeerTypeSameBotPM: () => this.chat.peerId.toUserId() === attachMenuBot.bot_id,
- attachMenuPeerTypeBotPM: () => this.chat.isBot,
- attachMenuPeerTypePM: () => this.chat.peerId.isUser(),
- attachMenuPeerTypeChat: () => this.chat.isAnyGroup,
- attachMenuPeerTypeBroadcast: () => this.chat.isBroadcast
- };
- for(const peerType of attachMenuBot.peer_types) {
- const verify = verifyMap[peerType._];
- found = await verify();
- if(found) {
- break;
- }
- }
- return found;
- }
- };
- return button;
- });
- buttons.splice(buttons.length, 0, ...attachMenuBotsButtons);
- this.attachMenuButtons.splice(0, this.attachMenuButtons.length, ...buttons);
- },
- onOpen: () => {
- this.emoticonsDropdown?.toggle(false);
- this.onMenuToggle?.(true);
- },
- onClose: () => {
- this.onMenuToggle?.(false);
- }
- });
- this.attachMenu.classList.add('attach-file');
- this.attachMenu.firstElementChild.replaceWith(Icon('attach'));
- // 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';
- this.newMessageWrapper.append(...[
- this.botCommandsToggle,
- this.btnToggleEmoticons,
- this.inputMessageContainer,
- this.btnScheduled,
- this.btnToggleReplyMarkup,
- this.attachMenu,
- this.recordTimeEl,
- this.fileInput
- ].filter(Boolean));
- if(this.replyElements?.container) this.rowsWrapper.append(this.replyElements.container);
- this.autocompleteHelperController = new AutocompleteHelperController();
- this.stickersHelper = new StickersHelper(this.rowsWrapper, this.autocompleteHelperController, this.chat, this.managers);
- this.emojiHelper = new EmojiHelper(this.rowsWrapper, this.autocompleteHelperController, this, this.managers);
- if(!this.excludeParts.commandsHelper) 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);
- this.rowsWrapper.append(this.newMessageWrapper);
- this.btnCancelRecord = this.createButtonIcon('binfilled btn-circle btn-record-cancel chat-input-secondary-button chat-secondary-button');
- 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 = this.createButtonIcon();
- this.btnSend.classList.add('btn-circle', 'btn-send', 'animated-button-icon');
- const icons: [Icon, string][] = [
- ['send', 'send'],
- ['schedule', 'schedule'],
- ['check', 'edit'],
- ['microphone_filled', 'record'],
- ['forward_filled', 'forward']
- ];
- this.btnSend.append(...icons.map(([name, type]) => Icon(name, 'animated-button-icon-icon', 'btn-send-icon-' + type)));
- this.addStarsBadge();
- this.btnSendContainer.append(this.recordRippleEl, this.btnSend);
- createRoot((dispose) => {
- this.chat.destroyMiddlewareHelper.onDestroy(dispose);
- const [effect, setEffect] = createSignal<DocId>();
- this.effect = effect;
- this.setEffect = setEffect;
- this.btnSendContainer.append(SelectedEffect({effect: this.effect}) as HTMLElement);
- });
- this.sendMenu = new SendMenu({
- onSilentClick: () => {
- this.sendSilent = true;
- this.sendMessage();
- },
- onScheduleClick: () => {
- this.scheduleSending(undefined);
- },
- onSendWhenOnlineClick: () => {
- this.setScheduleTimestamp(SEND_WHEN_ONLINE_TIMESTAMP, this.sendMessage.bind(this, true));
- },
- middleware: this.chat.destroyMiddlewareHelper.get(),
- openSide: 'top-left',
- onContextElement: this.btnSend,
- onOpen: () => {
- const good = this.chat.type !== ChatType.Scheduled && (!this.isInputEmpty() || !!Object.keys(this.forwarding).length) && !this.editMsgId;
- if(good) {
- this.emoticonsDropdown?.toggle(false);
- }
- return good;
- },
- canSendWhenOnline: this.canSendWhenOnline,
- onRef: (element) => {
- this.btnSendContainer.append(element);
- },
- withEffects: () => this.chat.peerId.isUser() && this.chat.peerId !== rootScope.myId,
- effect: this.effect,
- onEffect: this.setEffect
- });
- this.inputContainer.append(...[this.btnReaction, this.btnCancelRecord, this.btnSendContainer].filter(Boolean));
- if(this.btnToggleEmoticons) {
- this.emoticonsDropdown.attachButtonListener(this.btnToggleEmoticons, this.listenerSetter);
- this.listenerSetter.add(this.emoticonsDropdown)('open', this.onEmoticonsOpen);
- this.listenerSetter.add(this.emoticonsDropdown)('close', this.onEmoticonsClose);
- if(emoticonsDropdown === this.emoticonsDropdown) {
- createRoot((dispose) => {
- this.chat.destroyMiddlewareHelper.onDestroy(dispose);
- createEffect(() => {
- const shouldBeTop = windowSize.height >= 570 && windowSize.width > 600;
- this.emoticonsDropdown.getElement().classList.toggle('is-under', !shouldBeTop);
- });
- });
- }
- }
- this.attachMessageInputField();
- /* this.attachMenu.addEventListener('mousedown', (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();
- /* if(!rootScope.settings.stickers.suggest) {
- this.stickersHelper.checkEmoticon('');
- } else {
- this.onMessageInput();
- } */
- }
- this.messageInputField?.onFakeInput();
- });
- if(this.chat) {
- this.setChatListeners();
- }
- this.constructRecorder();
- this.updateSendBtn();
- this.listenerSetter.add(this.fileInput)('change', (e) => {
- const fileList = (e.target as HTMLInputElement & EventTarget).files;
- const files = Array.from(fileList).slice();
- this.fileSelectionPromise.resolve(files);
- if(!files.length) {
- return;
- }
- const newMediaPopup = getCurrentNewMediaPopup();
- if(newMediaPopup) {
- newMediaPopup.addFiles(files);
- } else {
- PopupElement.createPopup(PopupNewMedia, this.chat, files, this.willAttachType);
- }
- this.fileInput.value = '';
- }, false);
- attachClickEvent(this.btnSend, this.onBtnSendClick, {listenerSetter: this.listenerSetter, touchMouseDown: true});
- this.saveDraftDebounced = debounce(() => this.saveDraft(), 2500, false, true);
- const makeControlButton = (langKey: LangPackKey | HTMLElement) => {
- const button = Button('btn-primary btn-transparent text-bold chat-input-control-button');
- button.append(langKey instanceof HTMLElement ? langKey : i18n(langKey));
- return button;
- };
- this.botStartBtn = makeControlButton('BotStart');
- this.unblockBtn = makeControlButton('Unblock');
- this.joinBtn = this.chat.topbar && makeControlButton('ChannelJoin');
- this.onlyPremiumBtnText = new I18n.IntlElement({key: 'Chat.Input.PremiumRequiredButton', args: [0, document.createElement('a')]});
- this.onlyPremiumBtn = makeControlButton(this.onlyPremiumBtnText.element);
- attachClickEvent(this.botStartBtn, this.startBot, {listenerSetter: this.listenerSetter});
- attachClickEvent(this.unblockBtn, this.unblockUser, {listenerSetter: this.listenerSetter});
- attachClickEvent(this.onlyPremiumBtn, () => {
- PopupPremium.show();
- }, {listenerSetter: this.listenerSetter});
- this.joinBtn && attachClickEvent(this.joinBtn, this.chat.topbar.onJoinClick.bind(this.chat.topbar, this.joinBtn), {listenerSetter: this.listenerSetter});
- // * pinned part start
- this.pinnedControlBtn = Button('btn-primary btn-transparent text-bold chat-input-control-button', {icon: 'unpin'});
- this.listenerSetter.add(this.pinnedControlBtn)('click', () => {
- const peerId = this.chat.peerId;
- PopupElement.createPopup(PopupPinMessage, peerId, 0, true, () => {
- this.chat.appImManager.setPeer({isDeleting: true}); // * close tab
- // ! костыль, это скроет закреплённые сообщения сразу, вместо того, чтобы ждать пока анимация перехода закончится
- const originalChat = this.chat.appImManager.chat;
- if(originalChat.topbar.pinnedMessage) {
- originalChat.topbar.pinnedMessage.pinnedMessageContainer.toggle(true);
- }
- });
- });
- // * pinned part end
- this.openChatBtn = makeControlButton('OpenChat');
- attachClickEvent(this.openChatBtn, () => {
- this.chat.appImManager.setInnerPeer({
- peerId: this.chat.threadId
- });
- }, {listenerSetter: this.listenerSetter});
- this.controlContainer.append(...[
- this.botStartBtn,
- this.unblockBtn,
- this.joinBtn,
- this.onlyPremiumBtn,
- this.replyInTopicOverlay,
- this.pinnedControlBtn,
- this.openChatBtn
- ].filter(Boolean));
- }
- private setChatListeners() {
- this.listenerSetter.add(rootScope)('draft_updated', ({peerId, threadId, draft, force}) => {
- if(this.chat.threadId !== threadId || this.chat.peerId !== peerId || PEER_EXCEPTIONS.has(this.chat.type)) return;
- this.setDraft(draft, true, force);
- });
- this.listenerSetter.add(this.appImManager)('peer_changing', (chat) => {
- if(this.chat === chat && (this.chat.type === ChatType.Chat || this.chat.type === ChatType.Discussion)) {
- this.saveDraft();
- }
- });
- this.listenerSetter.add(this.appImManager)('chat_changing', ({from, to}) => {
- if(this.chat === from) {
- this.autocompleteHelperController.toggleListNavigation(false);
- } else if(this.chat === to) {
- this.autocompleteHelperController.toggleListNavigation(true);
- }
- });
- this.listenerSetter.add(rootScope)('scheduled_delete', ({peerId, mids}) => {
- if(this.chat.type === ChatType.Scheduled && this.chat.peerId === peerId && mids.includes(this.editMsgId)) {
- this.onMessageSent();
- }
- });
- this.listenerSetter.add(rootScope)('history_delete', ({peerId, msgs}) => {
- if(this.chat.peerId === peerId && !PEER_EXCEPTIONS.has(this.chat.type)) {
- if(msgs.has(this.editMsgId)) {
- this.onMessageSent();
- }
- if(this.replyToMsgId && msgs.has(this.replyToMsgId)) {
- this.clearHelper('reply');
- }
- /* if(this.chat.isStartButtonNeeded()) {
- this.setStartParam(BOT_START_PARAM);
- } */
- }
- });
- this.listenerSetter.add(rootScope)('dialogs_multiupdate', (dialogs) => {
- if(dialogs.has(this.chat.peerId) && (this.chat.type === ChatType.Chat || this.chat.type === ChatType.Discussion)) {
- if(this.startParam === BOT_START_PARAM) {
- this.setStartParam();
- } else { // updateNewMessage comes earlier than dialog appers
- this.center(true);
- }
- }
- });
- }
- public onAttachClick = async(documents?: boolean, photos?: boolean, videos?: boolean) => {
- if(await this.showSlowModeTooltipIfNeeded({
- element: this.attachMenu
- })) {
- return;
- }
- const promise = this.fileSelectionPromise = deferredPromise();
- this.fileInput.value = '';
- promise.finally(() => {
- idleController.removeEventListener('change', onIdleChange);
- if(promise !== this.fileSelectionPromise) {
- return;
- }
- });
- const onIdleChange = (idle: boolean) => {
- if(promise !== this.fileSelectionPromise) {
- promise.reject();
- return;
- }
- if(!idle) {
- setTimeout(() => {
- promise.reject();
- }, 1000);
- }
- };
- idleController.addEventListener('change', onIdleChange);
- if(documents) {
- this.fileInput.removeAttribute('accept');
- this.willAttachType = 'document';
- } else {
- const accept = [
- ...(photos ? IMAGE_MIME_TYPES_SUPPORTED : []),
- ...(videos ? VIDEO_MIME_TYPES_SUPPORTED : [])
- ].join(', ');
- this.fileInput.setAttribute('accept', accept || '*/*');
- this.willAttachType = 'media';
- }
- this.fileInput.click();
- this.onFileSelection?.(this.fileSelectionPromise);
- };
- public _center(neededFakeContainer: HTMLElement, animate?: boolean) {
- if(!neededFakeContainer && !this.inputContainer.classList.contains('is-centering')) {
- return;
- }
- if(neededFakeContainer === this.fakeWrapperTo) {
- return;
- }
- /* if(neededFakeContainer === this.botStartContainer && this.fakeWrapperTo === this.fakeSelectionWrapper) {
- 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 = 16;
- borderRadius = '' + (br + br * (1 - scale)) + 'px';
- }
- }
- // scale = widthTo / widthFrom;
- }
- // }
- this.fakeWrapperTo = neededFakeContainer;
- const duration = animate ? 200 : 0;
- SetTransition({
- element: this.inputContainer,
- className: 'is-centering',
- forwards,
- duration
- });
- SetTransition({
- element: this.rowsWrapperWrapper,
- className: 'is-centering-to-control',
- forwards: !!(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
- };
- }
- public async center(animate = false) {
- return this._center(await this.getNeededFakeContainer(), animate);
- }
- public setStartParam(startParam?: string) {
- if(this.startParam === startParam) {
- return;
- }
- this.startParam = startParam;
- this.center(true);
- }
- public unblockUser = () => {
- const toggle = this.toggleControlButtonDisability = toggleDisability([this.unblockBtn], true);
- const peerId = this.chat.peerId;
- const middleware = this.getMiddleware(() => {
- return this.chat.peerId === peerId && this.toggleControlButtonDisability === toggle;
- });
- this.managers.appUsersManager.toggleBlock(peerId, false).then(() => {
- if(middleware()) {
- toggle();
- this.toggleControlButtonDisability = undefined;
- }
- });
- };
- public startBot = () => {
- const {startParam} = this;
- const toggle = this.toggleControlButtonDisability = toggleDisability([this.botStartBtn], true);
- const peerId = this.chat.peerId;
- const middleware = this.getMiddleware(() => {
- return this.chat.peerId === peerId &&
- this.startParam === startParam &&
- this.toggleControlButtonDisability === toggle;
- });
- this.managers.appMessagesManager.startBot(peerId.toUserId(), undefined, startParam).then(() => {
- if(middleware()) {
- toggle();
- this.toggleControlButtonDisability = undefined;
- this.setStartParam();
- }
- });
- };
- public isReplyInTopicOverlayNeeded() {
- return REPLY_IN_TOPIC &&
- this.chat.isForum &&
- !this.chat.isForumTopic &&
- !this.replyToMsgId &&
- this.chat.type === ChatType.Chat;
- }
- public getJoinButtonType() {
- const {peerId, threadId} = this.chat;
- if(peerId.isUser()) {
- return;
- }
- const chat = apiManagerProxy.getChat(peerId.toChatId());
- if(!chat || !(chat as MTChat.channel).pFlags.left || (chat as MTChat.channel).pFlags.broadcast) {
- return;
- }
- if((chat as MTChat.channel).pFlags.join_request) {
- return 'request';
- }
- if((chat as MTChat.channel).pFlags.join_to_send || !threadId) {
- return 'join';
- }
- }
- public async getNeededFakeContainer(startParam = this.startParam) {
- if(this.chat.selection?.isSelecting) {
- return this.fakeSelectionWrapper;
- } else if(
- // startParam !== undefined || // * startParam isn't always should force control container, so it's commented
- // !(await this.chat.canSend()) || // ! WARNING, TEMPORARILY COMMENTED
- this.chat.type === ChatType.Pinned ||
- (this.chat.type === ChatType.Saved && this.chat.threadId !== this.chat.peerId) ||
- await this.chat.isStartButtonNeeded() ||
- this.isReplyInTopicOverlayNeeded() ||
- (this.chat.peerId.isUser() && (this.chat.isUserBlocked || this.chat.isPremiumRequired)) ||
- this.getJoinButtonType()
- ) {
- return this.controlContainer;
- }
- }
- // 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;
- // }
- // }
- private onCancelRecordClick = (e?: Event) => {
- if(e) {
- cancelEvent(e);
- }
- this.recordCanceled = true;
- this.recorder.stop();
- opusDecodeController.setKeepAlive(false);
- };
- private onEmoticonsToggle = (open: boolean) => {
- if(!this.btnToggleEmoticons) {
- return;
- }
- if(!IS_TOUCH_SUPPORTED) {
- this.btnToggleEmoticons.classList.toggle('active', open);
- } else {
- replaceButtonIcon(this.btnToggleEmoticons, open ? 'keyboard' : 'smile');
- }
- };
- private onEmoticonsOpen = () => {
- this.onEmoticonsToggle(true);
- };
- private onEmoticonsClose = () => {
- this.onEmoticonsToggle(false);
- };
- public getReadyToSend(callback: () => void) {
- return this.chat.type === ChatType.Scheduled ? (this.scheduleSending(callback), true) : (callback(), false);
- }
- public canSendWhenOnline = async() => {
- const peerId = this.chat.peerId;
- if(rootScope.myId === peerId || !peerId.isUser()) {
- return false;
- }
- if(!(await this.managers.appUsersManager.isUserOnlineVisible(peerId))) {
- return false;
- }
- const user = await this.managers.appUsersManager.getUser(peerId);
- return user.status?._ !== 'userStatusOnline';
- };
- public setScheduleTimestamp(timestamp: number, callback: () => void) {
- const middleware = this.getMiddleware();
- const minTimestamp = (Date.now() / 1000 | 0) + 10;
- if(timestamp <= minTimestamp) {
- timestamp = undefined;
- }
- this.scheduleDate = timestamp;
- callback();
- if(this.chat.type !== ChatType.Scheduled && this.chat.type !== ChatType.Stories && timestamp) {
- setTimeout(() => { // ! need timeout here because .forwardMessages will be called after timeout
- if(!middleware()) {
- return;
- }
- const popups = PopupElement.getPopups(PopupStickers);
- popups.forEach((popup) => popup.hide());
- this.appImManager.openScheduled(this.chat.peerId);
- }, 0);
- }
- }
- public getMiddleware(...args: Parameters<Chat['bubbles']['getMiddleware']>) {
- return this.chat.bubbles.getMiddleware(...args);
- }
- public scheduleSending = async(
- callback: () => void = this.sendMessage.bind(this, true),
- initDate = new Date()
- ) => {
- const middleware = this.getMiddleware();
- const canSendWhenOnline = await this.canSendWhenOnline();
- if(!middleware()) {
- return;
- }
- PopupElement.createPopup(PopupSchedule, {
- initDate,
- onPick: (timestamp) => {
- if(!middleware()) {
- return;
- }
- this.setScheduleTimestamp(timestamp, callback);
- },
- canSendWhenOnline
- }).show();
- };
- public async setUnreadCount() {
- if(!this.goDownUnreadBadge) {
- return;
- }
- const dialog = await this.managers.dialogsStorage.getAnyDialog(
- this.chat.peerId,
- this.chat.type === ChatType.Discussion ? undefined : this.chat.threadId
- );
- if(isSavedDialog(dialog)) {
- return;
- }
- const count = dialog?.unread_count;
- setBadgeContent(this.goDownUnreadBadge, '' + (count || ''));
- const isPeerLocalMuted = await this.managers.appNotificationsManager.isPeerLocalMuted({
- peerId: this.chat.peerId,
- respectType: true,
- threadId: this.chat.threadId
- });
- this.goDownUnreadBadge.classList.toggle('badge-gray', isPeerLocalMuted);
- if(this.goMentionUnreadBadge && this.chat.type === ChatType.Chat) {
- const hasMentions = !!(dialog?.unread_mentions_count && dialog.unread_count);
- setBadgeContent(this.goMentionUnreadBadge, hasMentions ? '' + (dialog.unread_mentions_count) : '');
- this.goMentionBtn.classList.toggle('is-visible', hasMentions);
- }
- if(this.goReactionUnreadBadge && this.chat.type === ChatType.Chat) {
- const hasReactions = !!dialog?.unread_reactions_count;
- setBadgeContent(this.goReactionUnreadBadge, hasReactions ? '' + (dialog.unread_reactions_count) : '');
- this.goReactionBtn.classList.toggle('is-visible', hasReactions);
- }
- }
- public getCurrentInputAsDraft(ignoreEmptyValue?: boolean) {
- const {value, entities} = getRichValueWithCaret(this.messageInputField.input, true, false);
- let draft: DraftMessage.draftMessage;
- if((value.length || ignoreEmptyValue) || this.replyToMsgId || this.willSendWebPage) {
- const webPage = this.willSendWebPage as WebPage.webPage;
- const webPageOptions = this.webPageOptions;
- const hasLargeMedia = !!webPage?.pFlags?.has_large_media;
- const replyTo = this.getReplyTo();
- draft = {
- _: 'draftMessage',
- date: tsNow(true),
- message: value.trim(),
- entities: entities.length ? entities : undefined,
- pFlags: {
- no_webpage: this.noWebPage,
- invert_media: this.invertMedia || undefined
- },
- reply_to: replyTo ? {
- _: 'inputReplyToMessage',
- reply_to_msg_id: replyTo.replyToMsgId,
- top_msg_id: this.chat.threadId,
- reply_to_peer_id: replyTo.replyToPeerId,
- ...(replyTo.replyToQuote && {
- quote_text: replyTo.replyToQuote.text,
- quote_entities: replyTo.replyToQuote.entities,
- quote_offset: replyTo.replyToQuote.offset
- })
- } : undefined,
- media: webPage ? {
- _: 'inputMediaWebPage',
- pFlags: {
- force_large_media: hasLargeMedia && webPageOptions?.largeMedia || undefined,
- force_small_media: hasLargeMedia && webPageOptions?.smallMedia || undefined,
- optional: true
- },
- url: webPage.url
- } : undefined,
- effect: this.effect()
- };
- }
- return draft;
- }
- public saveDraft() {
- if(
- !this.chat.peerId ||
- this.editMsgId ||
- PEER_EXCEPTIONS.has(this.chat.type)
- ) {
- return;
- }
- const draft = this.getCurrentInputAsDraft();
- this.managers.appDraftsManager.syncDraft(this.chat.peerId, this.chat.threadId, draft);
- }
- public mentionUser(peerId: PeerId, isHelper?: boolean) {
- Promise.resolve(this.managers.appPeersManager.getPeer(peerId)).then((peer) => {
- let str = '', entity: MessageEntity;
- const usernames = getPeerActiveUsernames(peer);
- if(usernames[0]) {
- str = '@' + usernames[0];
- } else {
- if(peerId.isUser()) {
- str = (peer as User.user).first_name || (peer as User.user).last_name;
- } else {
- str = (peer as MTChat.channel).title;
- }
- entity = {
- _: 'messageEntityMentionName',
- length: str.length,
- offset: 0,
- user_id: peer.id
- };
- }
- str += ' ';
- this.insertAtCaret(str, entity, isHelper);
- });
- }
- public destroy() {
- // this.chat.log.error('Input destroying');
- this.listenerSetter.removeAll();
- this.setCurrentHover();
- }
- public cleanup(helperToo = true) {
- if(this.chat && !this.chat.peerId) {
- this.chatInput.classList.add('hide');
- this.goDownBtn.classList.add('hide');
- }
- cancelSelection();
- this.lastTimeType = 0;
- this.startParam = undefined;
- if(this.toggleControlButtonDisability) {
- this.toggleControlButtonDisability();
- this.toggleControlButtonDisability = undefined;
- }
- if(this.messageInput) {
- this.clearInput();
- helperToo && this.clearHelper();
- }
- }
- public async setDraft(draft?: MyDraftMessage, fromUpdate = true, force = false) {
- if(
- (!force && !isInputEmpty(this.messageInput)) ||
- PEER_EXCEPTIONS.has(this.chat.type)
- ) {
- return false;
- }
- if(!draft) {
- draft = await this.managers.appDraftsManager.getDraft(this.chat.peerId, this.chat.threadId);
- if(!draft) {
- if(force) { // this situation can only happen when sending message with clearDraft
- /* const height = this.chatInput.getBoundingClientRect().height;
- 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);
- ((this.chat.bubbles.messagesQueuePromise || Promise.resolve()) as Promise<any>).then(() => {
- fastRaf(() => {
- this.onMessageSent();
- });
- });
- }
- return false;
- }
- }
- const wrappedDraft = wrapDraft(draft, {wrappingForPeerId: this.chat.peerId});
- const currentDraft = this.getCurrentInputAsDraft();
- const replyTo = draft.reply_to as InputReplyTo.inputReplyToMessage;
- const draftReplyToMsgId = replyTo?.reply_to_msg_id;
- if(draftsAreEqual(draft, currentDraft)) {
- return false;
- }
- if(fromUpdate) {
- this.clearHelper();
- }
- this.noWebPage = draft.pFlags.no_webpage;
- if(draftReplyToMsgId) {
- this.initMessageReply({
- replyToMsgId: draftReplyToMsgId,
- replyToPeerId: replyTo.reply_to_peer_id && getPeerId(replyTo.reply_to_peer_id),
- replyToQuote: replyTo.quote_text && {
- text: replyTo.quote_text,
- entities: replyTo.quote_entities,
- offset: replyTo.quote_offset
- }
- });
- }
- this.setInputValue(wrappedDraft, fromUpdate, fromUpdate, draft);
- return true;
- }
- private createSendAs() {
- this.sendAsPeerId = undefined;
- if(this.chat && (this.chat.type === ChatType.Chat || this.chat.type === ChatType.Discussion)) {
- let firstChange = true;
- this.sendAs = new ChatSendAs({
- managers: this.managers,
- onReady: (container, skipAnimation) => {
- let useRafs = 0;
- if(!container.parentElement) {
- this.newMessageWrapper.prepend(container);
- useRafs = 2;
- }
- this.updateOffset('as', true, skipAnimation, useRafs);
- },
- onChange: (sendAsPeerId) => {
- this.sendAsPeerId = sendAsPeerId;
- // do not change placeholder earlier than finishPeerChange does
- if(firstChange) {
- firstChange = false;
- return;
- }
- this.getPlaceholderParams().then((params) => {
- this.updateMessageInputPlaceholder(params);
- });
- }
- });
- } else {
- this.sendAs = undefined;
- }
- return this.sendAs;
- }
- public async finishPeerChange(options: Parameters<Chat['finishPeerChange']>[0]) {
- const {peerId, startParam, middleware} = options;
- const {
- forwardElements,
- btnScheduled,
- replyKeyboard,
- sendMenu,
- goDownBtn,
- chatInput,
- botCommandsToggle,
- attachMenu
- } = this;
- const previousSendAs = this.sendAs;
- const sendAs = this.createSendAs();
- const filteredAttachMenuButtons = this.filterAttachMenuButtons();
- const [
- isBroadcast,
- canPinMessage,
- isBot,
- canSend,
- canSendPlain,
- neededFakeContainer,
- ackedPeerFull,
- ackedScheduledMids,
- setSendAsCallback,
- peerTitleShort,
- isPremiumRequired
- ] = await Promise.all([
- this.managers.appPeersManager.isBroadcast(peerId),
- this.managers.appPeersManager.canPinMessage(peerId),
- this.managers.appPeersManager.isBot(peerId),
- this.chat?.canSend('send_messages') || true,
- this.chat?.canSend('send_plain') || true,
- this.getNeededFakeContainer(startParam),
- modifyAckedPromise(this.managers.acknowledged.appProfileManager.getProfileByPeerId(peerId)),
- btnScheduled ? modifyAckedPromise(this.managers.acknowledged.appMessagesManager.getScheduledMessages(peerId)) : undefined,
- sendAs ? (sendAs.setPeerId(peerId), sendAs.updateManual(true)) : undefined,
- wrapPeerTitle({peerId, onlyFirstName: true}),
- this.chat.isPremiumRequiredToContact()
- ]);
- const placeholderParams = this.messageInput ? await this.getPlaceholderParams(canSendPlain) : undefined;
- return () => {
- // console.warn('[input] finishpeerchange start');
- chatInput.classList.remove('hide');
- if(goDownBtn) {
- goDownBtn.classList.toggle('is-broadcast', isBroadcast);
- goDownBtn.classList.remove('hide');
- }
- if(this.goDownUnreadBadge) {
- this.setUnreadCount();
- }
- if(this.chat?.type === ChatType.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');
- callbackify(ackedScheduledMids.result, (mids) => {
- if(!middleware() || !mids) return;
- btnScheduled.classList.toggle('hide', !mids.length);
- });
- }
- if(this.newMessageWrapper) {
- this.updateOffset(null, false, true);
- }
- if(botCommandsToggle) {
- this.hasBotCommands = undefined;
- this.botMenuButton = undefined;
- this.botCommands.toggle(true, undefined, true);
- this.updateBotCommandsToggle(true);
- botCommandsToggle.remove();
- if(isBot) {
- const result = ackedPeerFull.result;
- callbackify(result, (userFull) => {
- if(!middleware()) return;
- this.updateBotCommands(userFull as UserFull.userFull, !(result instanceof Promise));
- });
- }
- }
- previousSendAs?.destroy();
- setSendAsCallback?.();
- replyKeyboard?.setPeer(peerId);
- sendMenu?.setPeerParams({peerId, isPaid: !!this.chat.starsAmount});
- let haveSomethingInControl = false;
- if(this.chat && this.joinBtn) {
- const type = this.getJoinButtonType();
- const good = !haveSomethingInControl && !!type;
- haveSomethingInControl ||= good;
- this.joinBtn.classList.toggle('hide', !good);
- this.joinBtn.replaceChildren(i18n(type === 'request' ? 'ChannelJoinRequest' : 'ChannelJoin'));
- }
- if(this.chat && this.pinnedControlBtn) {
- const good = !haveSomethingInControl && this.chat.type === ChatType.Pinned;
- haveSomethingInControl ||= good;
- this.pinnedControlBtn.classList.toggle('hide', !good);
- this.pinnedControlBtn.replaceChildren(i18n(canPinMessage ? 'Chat.Input.UnpinAll' : 'Chat.Pinned.DontShow'));
- }
- if(this.chat && this.openChatBtn) {
- const good = !haveSomethingInControl && this.chat.type === ChatType.Saved;
- haveSomethingInControl ||= good;
- if(good) {
- const savedPeerId = this.chat.threadId;
- const peer = apiManagerProxy.getPeer(savedPeerId);
- const key: LangPackKey = (peer as MTChat.channel).pFlags.broadcast ? 'OpenChannel2' : (savedPeerId.isUser() ? ((peer as User.user).pFlags.bot ? 'BotWebViewOpenBot' : 'OpenChat') : 'OpenGroup2');
- const span = i18n(key);
- this.openChatBtn.querySelector('.i18n').replaceWith(span);
- }
- this.openChatBtn.classList.toggle('hide', !good);
- }
- if(REPLY_IN_TOPIC && this.chat) {
- const good = !haveSomethingInControl && this.chat.isForum && !this.chat.isForumTopic && this.chat.type === ChatType.Chat;
- haveSomethingInControl ||= good;
- this.replyInTopicOverlay.classList.toggle('hide', !good);
- }
- if(this.chat && this.onlyPremiumBtn) {
- const good = !haveSomethingInControl && !isBot && peerId.isUser() && isPremiumRequired;
- haveSomethingInControl ||= good;
- this.onlyPremiumBtnText.compareAndUpdate({
- args: [peerTitleShort, this.onlyPremiumBtnText.args[1]]
- });
- this.onlyPremiumBtn.classList.toggle('hide', !good);
- }
- if(this.chat) {
- const good = !haveSomethingInControl && !isBot && peerId.isUser();
- haveSomethingInControl ||= good;
- this.unblockBtn.classList.toggle('hide', !good);
- }
- this.botStartBtn.classList.toggle('hide', haveSomethingInControl);
- if(this.messageInput) {
- this.updateMessageInput(
- canSend || haveSomethingInControl,
- canSendPlain,
- placeholderParams,
- peerId.isUser() ? options.text : undefined,
- peerId.isUser() ? options.entities : undefined
- );
- this.messageInput.dataset.peerId = '' + peerId;
- if(filteredAttachMenuButtons && attachMenu) {
- filteredAttachMenuButtons.then((visible) => {
- if(!middleware()) {
- return;
- }
- attachMenu.toggleAttribute('disabled', !visible.length);
- attachMenu.classList.toggle('btn-disabled', !visible.length);
- });
- }
- }
- this.messageInputField?.onFakeInput(undefined, true);
- // * testing
- // this.startParam = this.appPeersManager.isBot(peerId) ? '123' : undefined;
- this.startParam = startParam;
- this._center(neededFakeContainer, false);
- this.setStarsAmount(this.chat.starsAmount); // should reset when undefined
- // console.warn('[input] finishpeerchange ends');
- };
- }
- private updateOffset(
- type: ChatInput['hasOffset']['type'],
- forwards: boolean,
- skipAnimation?: boolean,
- useRafs?: number,
- applySameType?: boolean // ! WARNING
- ) {
- const prevOffset = this.hasOffset;
- const newOffset: ChatInput['hasOffset'] = {type, forwards};
- if(deepEqual(prevOffset, newOffset) && !applySameType) {
- return;
- }
- this.hasOffset = newOffset;
- if(type) {
- this.newMessageWrapper.dataset.offset = type;
- } else {
- delete this.newMessageWrapper.dataset.offset;
- }
- if(prevOffset?.forwards === newOffset.forwards && !applySameType) {
- return;
- }
- SetTransition({
- element: this.newMessageWrapper,
- className: 'has-offset',
- forwards,
- duration: skipAnimation ? 0 : 300,
- useRafs
- });
- }
- private updateBotCommands(userFull: UserFull.userFull, skipAnimation?: boolean) {
- const botInfo = userFull.bot_info;
- const menuButton = botInfo?.menu_button;
- this.hasBotCommands = !!botInfo?.commands?.length;
- this.botMenuButton = menuButton?._ === 'botMenuButton' ? menuButton : undefined;
- replaceContent(this.botCommandsView, this.botMenuButton ? wrapEmojiText(this.botMenuButton.text) : '');
- this.botCommandsIcon.classList.toggle('hide', !!this.botMenuButton);
- this.botCommandsView.classList.toggle('hide', !this.botMenuButton);
- this.botCommandsToggle.classList.toggle('is-view', !!this.botMenuButton);
- this.updateBotCommandsToggle(skipAnimation);
- }
- private updateBotCommandsToggle(skipAnimation?: boolean) {
- const {botCommandsToggle, hasBotCommands, botMenuButton} = this;
- const isNeeded = !!(hasBotCommands || botMenuButton);
- const isInputEmpty = this.isInputEmpty();
- const show = isNeeded && (isInputEmpty || !!botMenuButton);
- if(!isNeeded) {
- if(!botCommandsToggle.parentElement) {
- return;
- }
- botCommandsToggle.remove();
- }
- const forwards = show;
- const useRafs = botCommandsToggle.parentElement ? 0 : 2;
- if(botMenuButton && isInputEmpty) {
- // padding + icon size + icon margin
- const width = getTextWidth(botMenuButton.text, FontFull) + 22 + 20 + 6;
- this.newMessageWrapper.style.setProperty('--commands-size', `${Math.ceil(width)}px`);
- } else {
- // this.newMessageWrapper.style.setProperty('--commands-size', `38px`);
- this.newMessageWrapper.style.removeProperty('--commands-size');
- }
- if(!botCommandsToggle.parentElement) {
- this.newMessageWrapper.prepend(botCommandsToggle);
- }
- this.updateOffset('commands', forwards, skipAnimation, useRafs, true);
- }
- private async getPlaceholderParams(canSend?: boolean): Promise<Parameters<ChatInput['updateMessageInputPlaceholder']>[0]> {
- canSend ??= await this.chat.canSend('send_plain');
- const {peerId, threadId, isForum, type} = this.chat;
- let key: LangPackKey, args: FormatterArguments, inputStarsCountEl: HTMLElement;
- if(!canSend) {
- key = 'Channel.Persmission.MessageBlock';
- } else if(threadId && !isForum && !peerId.isUser()) {
- 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 if(type === ChatType.Stories) {
- key = 'Story.ReplyPlaceholder';
- } else if(isForum && type === ChatType.Chat && !threadId) {
- const topic = await this.managers.dialogsStorage.getForumTopic(peerId, GENERAL_TOPIC_ID);
- if(topic) {
- key = 'TypeMessageIn';
- args = [wrapEmojiText(topic.title)];
- } else {
- key = 'Message';
- }
- } else if(this.chat.starsAmount) {
- key = 'PaidMessages.MessageForStars';
- const starsElement = document.createElement('span');
- const span = inputStarsCountEl = document.createElement('span');
- starsElement.append(Icon('star', 'input-message-placeholder-stars'), span);
- args = [starsElement];
- } else {
- key = 'Message';
- }
- return {key, args, inputStarsCountEl};
- }
- private updateMessageInputPlaceholder({key, args = [], inputStarsCountEl}: {key: LangPackKey, args?: FormatterArguments, inputStarsCountEl?: HTMLElement}) {
- // console.warn('[input] update placeholder');
- // const i = I18n.weakMap.get(this.messageInput) as I18n.IntlElement;
- const i = I18n.weakMap.get(this.messageInputField.placeholder) as I18n.IntlElement;
- if(!i) {
- return;
- }
- const oldKey = i.key;
- const oldArgs = i.args;
- i.compareAndUpdateBool({key, args}) &&
- this.starsState.set({inputStarsCountEl});
- return {oldKey, oldArgs};
- }
- private filterAttachMenuButtons() {
- if(!this.attachMenuButtons) return;
- return filterAsync(this.attachMenuButtons, (button) => {
- return button.verify ? button.verify() : true;
- });
- }
- public updateMessageInput(
- canSend: boolean,
- canSendPlain: boolean,
- placeholderParams: Parameters<ChatInput['updateMessageInputPlaceholder']>[0],
- text?: string,
- entities?: MessageEntity[]
- ) {
- const {chatInput, messageInput} = this;
- const isHidden = chatInput.classList.contains('is-hidden');
- const willBeHidden = !canSend;
- if(isHidden !== willBeHidden) {
- chatInput.classList.add('no-transition');
- chatInput.classList.toggle('is-hidden', !canSend);
- void chatInput.offsetLeft; // reflow
- chatInput.classList.remove('no-transition');
- }
- const isEditingAndLocked = canSend && !canSendPlain && this.restoreInputLock;
- !isEditingAndLocked && this.updateMessageInputPlaceholder(placeholderParams);
- if(isEditingAndLocked) {
- this.restoreInputLock = () => {
- this.updateMessageInputPlaceholder(placeholderParams);
- this.messageInput.contentEditable = 'false';
- };
- } else if(!canSend || !canSendPlain) {
- messageInput.contentEditable = 'false';
- if(!canSendPlain) {
- this.messageInputField.onFakeInput(undefined, true);
- }
- } else {
- this.restoreInputLock = undefined;
- messageInput.contentEditable = 'true';
- if(text) {
- this.managers.appDraftsManager.setDraft(this.chat.peerId, undefined, text, entities);
- }
- this.setDraft(undefined, false);
- if(!messageInput.innerHTML) {
- this.messageInputField.onFakeInput(undefined, true);
- }
- }
- this.updateSendBtn();
- }
- private attachMessageInputField() {
- const oldInputField = this.messageInputField;
- this.messageInputField = new InputFieldAnimated({
- placeholder: 'Message',
- // placeholderAsElement: true,
- name: 'message',
- withLinebreaks: true
- });
- this.messageInputField.input.tabIndex = -1;
- 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;
- this.attachMessageInputListeners();
- createMarkdownCache(this.messageInput);
- if(IS_STICKY_INPUT_BUGGED) {
- fixSafariStickyInputFocusing(this.messageInput);
- }
- if(oldInputField) {
- oldInputField.input.replaceWith(this.messageInputField.input);
- oldInputField.placeholder.replaceWith(this.messageInputField.placeholder);
- oldInputField.inputFake.replaceWith(this.messageInputField.inputFake);
- } else {
- this.inputMessageContainer.append(this.messageInputField.input, this.messageInputField.placeholder, this.messageInputField.inputFake);
- }
- }
- public passEventToInput(e: KeyboardEvent): void {
- if(!isSendShortcutPressed(e)) return void focusInput(this.messageInput, e);
- this.sendMessage();
- document.addEventListener('keyup', () => {
- focusInput(this.messageInput);
- }, {once: true});
- }
- private attachMessageInputListeners() {
- this.listenerSetter.add(this.messageInput)('keydown', (e) => {
- const key = e.key;
- if(isSendShortcutPressed(e)) {
- cancelEvent(e);
- this.sendMessage();
- } else if(e.ctrlKey || e.metaKey) {
- handleMarkdownShortcut(this.messageInput, e);
- } else if((key === 'PageUp' || key === 'PageDown') && !e.shiftKey) { // * fix pushing page to left (Chrome Windows)
- e.preventDefault();
- if(key === 'PageUp') {
- 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);
- }
- }
- });
- attachClickEvent(this.messageInput, (e) => {
- if(!this.canSendPlain()) {
- toastNew({
- langPackKey: POSTING_NOT_ALLOWED_MAP['send_plain']
- });
- return;
- }
- // const checkPseudoElementClick = (e: MouseEvent, tag: 'after' | 'before') => {
- // const target = (e.currentTarget || e.target) as HTMLElement;
- // const pseudo = getComputedStyle(target, `:${tag}`);
- // if(!pseudo) {
- // return false;
- // }
- // const [atop, aheight, aleft, awidth] = ['top', 'height', 'left', 'width'].map((k) => pseudo.getPropertyValue(k).slice(0, -2));
- // const ex = (e as any).layerX;
- // const ey = (e as any).layerY;
- // if(ex > aleft && ex < (aleft + awidth) && ey > atop && ey < (atop + aheight)) {
- // return true;
- // }
- // return false;
- // };
- const checkIconClick = (e: MouseEvent, quote: HTMLElement) => {
- const rect = quote.getBoundingClientRect();
- const ex = e.clientX;
- const ey = e.clientY;
- const elementWidth = 20;
- const elementHeight = 20;
- if(ex > (rect.right - elementWidth) && ex < rect.right && ey > rect.top && ey < (rect.top + elementHeight)) {
- return true;
- }
- return false;
- };
- const quote = findUpClassName(e.target, 'can-send-collapsed');
- if(quote && checkIconClick(e, quote)) {
- if(quote.dataset.collapsed) delete quote.dataset.collapsed;
- else quote.dataset.collapsed = '1';
- toastNew({langPackKey: quote.dataset.collapsed ? 'Input.Quote.Collapsed' : 'Input.Quote.Expanded'});
- return;
- }
- }, {listenerSetter: this.listenerSetter});
- if(IS_TOUCH_SUPPORTED) {
- attachClickEvent(this.messageInput, (e) => {
- if(this.emoticonsDropdown.isActive()) {
- this.emoticonsDropdown.toggle(false);
- blurActiveElement();
- cancelEvent(e);
- // this.messageInput.focus();
- return;
- }
- if(!this.chat.isStandalone) {
- this.appImManager.selectTab(APP_TABS.CHAT); // * set chat tab for album orientation
- }
- // this.saveScroll();
- }, {listenerSetter: this.listenerSetter});
- /* this.listenerSetter.add(window)('resize', () => {
- this.restoreScroll();
- }); */
- /* if(isSafari) {
- this.listenerSetter.add(this.messageInput)('mousedown', () => {
- window.requestAnimationFrame(() => {
- window.requestAnimationFrame(() => {
- emoticonsDropdown.toggle(false);
- });
- });
- });
- } */
- }
- /* this.listenerSetter.add(this.messageInput)('beforeinput', (e: Event) => {
- // * 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();
- });
- this.listenerSetter.add(this.messageInput)('focusin', () => {
- this.isFocused = true;
- // this.updateSendBtn();
- if((this.chat.type === ChatType.Chat || this.chat.type === ChatType.Discussion) &&
- this.chat.bubbles.scrollable.loadedAll.bottom) {
- this.managers.appMessagesManager.readAllHistory(this.chat.peerId, this.chat.threadId);
- }
- this.onFocusChange?.(true);
- });
- this.listenerSetter.add(this.messageInput)('focusout', () => {
- this.isFocused = false;
- // this.updateSendBtn();
- this.onFocusChange?.(false);
- });
- }
- public canSendPlain() {
- return this.messageInput.isContentEditable && !this.chatInput.classList.contains('is-hidden');
- }
- public onMessageInput = (e?: Event) => {
- // * validate due to manual formatting through browser's context menu
- /* const inputType = (e as InputEvent).inputType;
- 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: markdownEntities1, caretPos} = getRichValueWithCaret(this.messageInputField.input);
- // const entities = parseEntities(value);
- const [value, markdownEntities] = parseMarkdown(richValue, markdownEntities1, true);
- const entities = mergeEntities(markdownEntities, parseEntities(value));
- this.throttledSetMessageCountToBadgeState(richValue);
- maybeClearUndoHistory(this.messageInput);
- this.processWebPage(richValue, entities);
- const isEmpty = !richValue.trim();
- if(isEmpty) {
- if(this.lastTimeType) {
- this.managers.appMessagesManager.setTyping(this.chat.peerId, {_: 'sendMessageCancelAction'}, undefined, this.chat.threadId);
- }
- MarkupTooltip.getInstance().hide();
- // * 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 && !IS_MOBILE) {
- setTimeout(() => {
- if(document.activeElement === this.messageInput) {
- this.messageInput.textContent = '1';
- placeCaretAtEnd(this.messageInput);
- this.messageInput.textContent = '';
- }
- }, 0);
- }
- } else {
- const time = Date.now();
- if((time - this.lastTimeType) >= 6000 && e?.isTrusted) {
- this.lastTimeType = time;
- this.managers.appMessagesManager.setTyping(this.chat.peerId, {_: 'sendMessageTypingAction'}, undefined, this.chat.threadId);
- }
- this.botCommands?.toggle(true);
- }
- if(this.botCommands) {
- this.updateBotCommandsToggle();
- }
- if(!this.editMsgId && !this.processingDraftMessage) {
- this.saveDraftDebounced();
- }
- this.checkAutocomplete(richValue, caretPos, entities);
- processCurrentFormatting(this.messageInput);
- this.updateSendBtn();
- };
- private processWebPage(
- richValue: string,
- entities: MessageEntity[],
- message: Message.message | DraftMessage.draftMessage = this.processingDraftMessage || this.editMessage
- ) {
- const messageMedia = message?.media;
- const invertMedia = message?.pFlags?.invert_media;
- const webPageUrl = messageMedia?._ === 'inputMediaWebPage' ?
- messageMedia.url :
- ((messageMedia as MessageMedia.messageMediaWebPage)?.webpage as WebPage.webPage)?.url;
- const urlEntities: Array<MessageEntity.messageEntityUrl | MessageEntity.messageEntityTextUrl> =
- (!messageMedia || webPageUrl) &&
- entities.filter((e) => e._ === 'messageEntityUrl' || e._ === 'messageEntityTextUrl') as any;
- if(!urlEntities?.length) {
- if(this.lastUrl) {
- this.lastUrl = '';
- delete this.noWebPage;
- this.willSendWebPage = null;
- if(this.helperType) {
- this.helperFunc();
- } else {
- this.clearHelper();
- }
- }
- return;
- }
- let foundUrl = webPageUrl;
- if(!foundUrl) 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;
- }
- }
- foundUrl = url;
- break;
- }
- if(this.lastUrl === foundUrl) {
- return;
- }
- if(!foundUrl) {
- if(this.willSendWebPage) {
- this.onHelperCancel();
- }
- return;
- }
- this.lastUrl = foundUrl;
- const oldWebPage = webPageUrl;
- const promise = this.getWebPagePromise = Promise.all([
- this.managers.appWebPagesManager.getWebPage(foundUrl),
- this.chat.canSend('embed_links')
- ]).then(([webPage, canEmbedLinks]) => {
- if(this.getWebPagePromise === promise) this.getWebPagePromise = undefined;
- if(this.lastUrl !== foundUrl) return;
- if(webPage?._ === 'webPage' && canEmbedLinks) {
- const newReply = this.setTopInfo({
- type: 'webpage',
- callerFunc: () => {},
- title: webPage.site_name || webPage.title || 'Webpage',
- subtitle: webPage.description || webPage.url || ''
- });
- this.setCurrentHover(this.webPageHover, newReply);
- delete this.noWebPage;
- this.willSendWebPage = webPage;
- if(this.webPageElements) {
- const positionElement = oldWebPage && invertMedia ? this.webPageElements.above : this.webPageElements.below;
- positionElement.checkboxField.checked = true;
- const sizeElement = oldWebPage && (messageMedia as MessageMedia.messageMediaWebPage).pFlags.force_small_media ? this.webPageElements.smaller : this.webPageElements.larger;
- sizeElement.checkboxField.checked = true;
- const sizeGroupContainer = sizeElement.element.parentElement;
- sizeGroupContainer.classList.toggle('hide', !webPage.pFlags.has_large_media);
- }
- this.webPageOptions = {
- optional: true,
- ...(oldWebPage ? {
- smallMedia: oldWebPage && (messageMedia as MessageMedia.messageMediaWebPage).pFlags.force_small_media || undefined,
- largeMedia: oldWebPage && (messageMedia as MessageMedia.messageMediaWebPage).pFlags.force_large_media || undefined
- } : {})
- };
- } else if(this.willSendWebPage) {
- this.onHelperCancel();
- }
- });
- }
- public insertAtCaret(insertText: string, insertEntity?: MessageEntity, isHelper = true) {
- if(!this.canSendPlain()) {
- toastNew({
- langPackKey: POSTING_NOT_ALLOWED_MAP['send_plain']
- });
- return;
- }
- RichInputHandler.getInstance().makeFocused(this.messageInput);
- 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;
- if(isHelper && caretPos !== -1) {
- const match = matches ? matches[2] : fullValue;
- // const {node, selection} = getCaretPosNew(this.messageInput);
- const selection = document.getSelection();
- // const range = document.createRange();
- let counter = 0;
- while(selection.toString() !== match) {
- if(++counter >= 10000) {
- throw new Error('lolwhat');
- }
- // for(let i = 0; i < match.length; ++i) {
- selection.modify('extend', 'backward', 'character');
- }
- }
- {
- // const fragment = wrapDraftText(insertText, {entities: insertEntity ? [insertEntity] : undefined, wrappingForPeerId: this.chat.peerId});
- insertRichTextAsHTML(this.messageInput, insertText, insertEntity ? [insertEntity] : undefined, this.chat.peerId);
- // const {node, offset} = getCaretPos(this.messageInput);
- // const fragmentLastChild = fragment.lastChild;
- // if(node?.nodeType === node.TEXT_NODE) {
- // const prefix = node.nodeValue.slice(0, offset);
- // const suffix = node.nodeValue.slice(offset);
- // const suffixNode = document.createTextNode(suffix);
- // node.nodeValue = prefix;
- // node.parentNode.insertBefore(suffixNode, node.nextSibling);
- // node.parentNode.insertBefore(fragment, suffixNode);
- // setCaretAt(fragmentLastChild.nextSibling);
- // this.messageInputField.simulateInputEvent();
- // }
- }
- // return;
- // // merge emojis
- // const hadEntities = parseEntities(fullValue);
- // mergeEntities(entities, hadEntities);
- // // 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
- // const diff = matches ? insertLength - matches[2].length : insertLength;
- // entities.forEach((entity) => {
- // if(entity.offset >= matchIndex) {
- // entity.offset += diff;
- // }
- // });
- // mergeEntities(entities, addEntities);
- // 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);
- // }
- // // const saveExecuted = this.prepareDocumentExecute();
- // // can't exec .value here because it will instantly check for autocomplete
- // const value = documentFragmentToHTML(wrapDraftText(newValue, {entities}));
- // this.messageInputField.setValueSilently(value);
- // const caret = this.messageInput.querySelector('.composer-sel');
- // if(caret) {
- // setCaretAt(caret);
- // caret.remove();
- // }
- // // but it's needed to be checked only here
- // this.onMessageInput();
- // // saveExecuted();
- // // document.execCommand('insertHTML', true, wrapEmojiText(emoji));
- }
- public onEmojiSelected = (emoji: ReturnType<typeof getEmojiFromElement>, autocomplete: boolean) => {
- const entity: MessageEntity = emoji.docId ? {
- _: 'messageEntityCustomEmoji',
- document_id: emoji.docId,
- length: emoji.emoji.length,
- offset: 0
- } : getEmojiEntityFromEmoji(emoji.emoji);
- this.insertAtCaret(emoji.emoji, entity, autocomplete);
- return true;
- };
- private async checkAutocomplete(value?: string, caretPos?: number, entities?: MessageEntity[]) {
- // return;
- const hadValue = value !== undefined;
- if(!hadValue) {
- const r = getRichValueWithCaret(this.messageInputField.input, true, true);
- value = r.value;
- caretPos = r.caretPos;
- entities = r.entities;
- }
- if(caretPos === -1) {
- caretPos = value.length;
- }
- if(entities === undefined || !hadValue) {
- const [_value, newEntities] = parseMarkdown(value, entities, true);
- entities = mergeEntities(newEntities, parseEntities(_value));
- }
- value = value.slice(0, caretPos);
- 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];
- let query = matches[2];
- const firstChar = query[0];
- if(
- this.stickersHelper &&
- rootScope.settings.stickers.suggest !== 'none' &&
- await this.chat.canSend('send_stickers') &&
- (['messageEntityEmoji', 'messageEntityCustomEmoji'] as MessageEntity['_'][]).includes(entity?._) &&
- entity.length === value.length &&
- !entity.offset
- ) {
- foundHelper = this.stickersHelper;
- this.stickersHelper.checkEmoticon(value);
- } else if(firstChar === '@') { // mentions
- const topMsgId = this.chat.threadId ? getServerMessageId(this.chat.threadId) : undefined;
- const result = this.mentionsHelper.checkQuery(
- query,
- this.chat.peerId.isUser() ? NULL_PEER_ID : this.chat.peerId,
- topMsgId,
- this.globalMentions
- );
- if(result) {
- foundHelper = this.mentionsHelper;
- }
- } else if(!matches[1] && firstChar === '/') { // commands
- if(this.commandsHelper && await this.commandsHelper.checkQuery(query, this.chat.peerId)) {
- foundHelper = this.commandsHelper;
- }
- } else if(rootScope.settings.emoji.suggest) { // emoji
- query = query.replace(/^\s*/, '');
- if(!value.match(/^\s*:(.+):\s*$/) && !value.match(/:[;!@#$%^&*()-=|]/) && query) {
- foundHelper = this.emojiHelper;
- this.emojiHelper.checkQuery(query, firstChar);
- }
- }
- }
- let canSendInline: boolean;
- if(!foundHelper) {
- canSendInline = await this.chat.canSend('send_inline');
- }
- foundHelper = this.checkInlineAutocomplete(value, canSendInline, foundHelper);
- this.autocompleteHelperController.hideOtherHelpers(foundHelper);
- }
- private checkInlineAutocomplete(value: string, canSendInline: boolean, foundHelper?: AutocompleteHelper): AutocompleteHelper {
- let needPlaceholder = false;
- const setPreloaderShow = (show: boolean) => {
- if(!this.btnPreloader) {
- return;
- }
- if(show && !canSendInline) {
- show = false;
- }
- SetTransition({
- element: this.btnPreloader,
- className: 'show',
- forwards: show,
- duration: 400
- });
- };
- 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 = this.createButtonIcon('none btn-preloader float show disable-hover', {noRipple: true});
- putPreloader(this.btnPreloader, true);
- this.inputMessageContainer.parentElement.insertBefore(this.btnPreloader, this.inputMessageContainer.nextSibling);
- } else {
- setPreloaderShow(true);
- }
- this.inlineHelper.checkQuery(this.chat.peerId, username, query, canSendInline).then(({user, renderPromise}) => {
- if(needPlaceholder && user.bot_inline_placeholder) {
- this.messageInput.dataset.inlinePlaceholder = user.bot_inline_placeholder;
- }
- renderPromise.then(() => {
- setPreloaderShow(false);
- });
- }).catch((err: ApiError) => {
- setPreloaderShow(false);
- });
- }
- }
- if(!needPlaceholder) {
- delete this.messageInput.dataset.inlinePlaceholder;
- }
- if(foundHelper !== this.inlineHelper) {
- setPreloaderShow(false);
- }
- return foundHelper;
- }
- private setRecording(value: boolean) {
- if(this.recording === value) {
- return;
- }
- this.recording = value;
- this.starsState.set({isRecording: value});
- this.setShrinking(this.recording, ['is-recording']);
- this.updateSendBtn();
- this.onRecording?.(value);
- }
- public setShrinking(value?: boolean, classNames?: string[]) {
- value ||= this.recording;
- SetTransition({
- element: this.chatInput,
- className: 'is-shrinking' + (classNames ? ' ' + classNames.join(' ') : ''),
- forwards: value,
- duration: 200
- });
- }
- public setCanForwardStory(value: boolean) {
- // * true, because forward button will be hidden if it's a private story
- // * this is to correctly animate the button on changing story
- this.canForwardStory = value || true;
- this.updateSendBtn();
- }
- public async showSlowModeTooltipIfNeeded({
- container,
- element,
- sendingFew,
- textOverflow
- }: {
- container?: HTMLElement,
- element?: HTMLElement,
- sendingFew?: boolean,
- textOverflow?: boolean
- } = {}) {
- const {peerId} = this.chat;
- if(peerId.isUser()) {
- return false;
- }
- const chatId = peerId.toChatId();
- const chat = apiManagerProxy.getChat(chatId) as MTChat.channel;
- if(!chat.pFlags.slowmode_enabled) {
- return false;
- }
- let textElement: HTMLElement, onClose: () => void;
- if(textOverflow) {
- textElement = i18n('SlowmodeSendErrorTooLong');
- } else if(sendingFew) {
- textElement = i18n('SlowmodeSendError');
- } else if(await this.managers.appMessagesManager.hasOutgoingMessage(peerId)) {
- textElement = i18n('SlowmodeSendError');
- } else {
- const chatFull = await this.managers.appProfileManager.getChatFull(chatId) as ChatFull.channelFull;
- const getLeftDuration = () => Math.max(0, (chatFull.slowmode_next_send_date || 0) - tsNow(true));
- if(!getLeftDuration()) {
- return false;
- }
- const s = document.createElement('span');
- onClose = eachSecond(() => {
- const leftDuration = getLeftDuration();
- s.replaceChildren(wrapSlowModeLeftDuration(leftDuration));
- if(!leftDuration) {
- close();
- }
- }, true);
- textElement = i18n('SlowModeHint', [s]);
- }
- const {close} = showTooltip({
- element: element || this.btnSendContainer,
- vertical: 'top',
- container: container || this.btnSendContainer.parentElement,
- textElement,
- onClose: () => {
- onClose?.();
- this.emoticonsDropdown.setIgnoreMouseOut('tooltip', false);
- },
- auto: true
- });
- this.emoticonsDropdown.setIgnoreMouseOut('tooltip', true);
- return true;
- }
- private onBtnSendClick = async(e: Event) => {
- cancelEvent(e);
- const isInputEmpty = this.isInputEmpty();
- if(this.chat.type === ChatType.Stories && isInputEmpty && !this.freezedFocused && this.canForwardStory) {
- this.forwardStoryCallback?.(e as MouseEvent);
- return;
- } else if(!this.recorder || this.recording || !isInputEmpty || this.forwarding || this.editMsgId) {
- if(this.recording) {
- if((Date.now() - this.recordStartTime) < RECORD_MIN_TIME) {
- this.onCancelRecordClick();
- } else {
- this.recorder.stop();
- }
- } else {
- this.sendMessage();
- }
- } else {
- const isAnyChat = this.chat.peerId.isAnyChat();
- const flag: ChatRights = 'send_voices';
- if(isAnyChat && !(await this.chat.canSend(flag))) {
- toastNew({langPackKey: POSTING_NOT_ALLOWED_MAP[flag]});
- return;
- }
- if(await this.showSlowModeTooltipIfNeeded()) {
- return;
- }
- this.chatInput.classList.add('is-locked');
- blurActiveElement();
- let restricted = false;
- if(!isAnyChat) {
- const userFull = await this.managers.appProfileManager.getProfile(this.chat.peerId.toUserId());
- if(userFull?.pFlags.voice_messages_forbidden) {
- toastNew({
- langPackKey: 'Chat.SendVoice.PrivacyError',
- langPackArguments: [await wrapPeerTitle({peerId: this.chat.peerId})]
- });
- restricted = true;
- }
- }
- if(restricted) {
- this.chatInput.classList.remove('is-locked');
- return;
- }
- this.recorder.start().then(() => {
- this.releaseMediaPlayback = appMediaPlaybackController.setSingleMedia();
- this.recordCanceled = false;
- this.setRecording(true);
- opusDecodeController.setKeepAlive(true);
- const showDiscardPopup = () => {
- PopupElement.createPopup(PopupPeer, 'popup-cancel-record', {
- titleLangKey: 'DiscardVoiceMessageTitle',
- descriptionLangKey: 'DiscardVoiceMessageDescription',
- buttons: [{
- langKey: 'DiscardVoiceMessageAction',
- callback: () => {
- simulateClickEvent(this.btnCancelRecord);
- }
- }, {
- langKey: 'Continue',
- isCancel: true
- }]
- }).show();
- };
- this.recordingOverlayListener = this.listenerSetter.add(document.body)('mousedown', (e) => {
- if(!findUpClassName(e.target, CLASS_NAME) && !findUpClassName(e.target, 'popup-cancel-record')) {
- cancelEvent(e);
- showDiscardPopup();
- }
- }, {capture: true, passive: false}) as any;
- appNavigationController.pushItem(this.recordingNavigationItem = {
- type: 'voice',
- onPop: () => {
- setTimeout(() => {
- showDiscardPopup();
- }, 0);
- return false;
- }
- });
- 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;
- const r = () => {
- if(!this.recording) return;
- analyser.getByteFrequencyData(frequencyData);
- let sum = 0;
- frequencyData.forEach((value) => {
- sum += value;
- });
- const percents = Math.min(1, (sum / max) + min);
- // console.log('frequencyData', frequencyData, percents);
- this.recordRippleEl.style.transform = `scale(${percents})`;
- // this.recordRippleEl.style.transform = `scale(0.8)`;
- const diff = Date.now() - this.recordStartTime;
- const ms = diff % 1000;
- const formatted = toHHMMSS(diff / 1000) + ',' + ('00' + Math.round(ms / 10)).slice(-2);
- this.recordTimeEl.textContent = formatted;
- fastRaf(r);
- };
- 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;
- }
- this.setRecording(false);
- this.chatInput.classList.remove('is-locked');
- });
- }
- };
- public onHelperCancel = async(e?: Event, force?: boolean) => {
- if(e) {
- cancelEvent(e);
- }
- if(this.willSendWebPage) {
- const lastUrl = this.lastUrl;
- let needReturn = false;
- if(this.helperType) {
- // if(this.helperFunc) {
- await this.helperFunc();
- // }
- needReturn = true;
- }
- // * restore values
- this.lastUrl = lastUrl;
- this.noWebPage = true;
- this.willSendWebPage = null;
- if(needReturn) return;
- }
- if(this.helperType === 'edit' && !force) {
- const message = this.editMessage;
- const draft = this.getCurrentInputAsDraft(true);
- if(draft) {
- delete draft.pFlags.no_webpage;
- }
- const replyTo = message.reply_to?._ === 'messageReplyHeader' ? message.reply_to : undefined;
- const messageMedia = message?.media?._ === 'messageMediaWebPage' ? message.media : undefined;
- const hasLargeMedia = (messageMedia?.webpage as WebPage.webPage)?.pFlags?.has_large_media;
- const originalDraft: DraftMessage.draftMessage = {
- _: 'draftMessage',
- date: draft?.date,
- message: message.message,
- entities: message.entities,
- pFlags: {
- invert_media: message.pFlags.invert_media
- },
- media: messageMedia && {
- _: 'inputMediaWebPage',
- pFlags: {
- force_large_media: hasLargeMedia && messageMedia.pFlags.force_large_media || undefined,
- force_small_media: hasLargeMedia && messageMedia.pFlags.force_small_media || undefined,
- optional: true
- },
- url: (messageMedia.webpage as WebPage.webPage).url
- },
- reply_to: replyTo && {
- _: 'inputReplyToMessage',
- reply_to_msg_id: replyTo.reply_to_msg_id
- }
- };
- if(originalDraft.entities?.length || draft?.entities?.length) {
- const canPassEntitiesTypes = new Set(Object.values(MARKDOWN_ENTITIES));
- canPassEntitiesTypes.add('messageEntityCustomEmoji');
- if(originalDraft?.entities) {
- originalDraft.entities = originalDraft.entities.slice();
- }
- [originalDraft, draft].forEach((draft) => {
- if(!draft?.entities) {
- return;
- }
- forEachReverse(draft.entities, (entity, idx, arr) => {
- if(!canPassEntitiesTypes.has(entity._)) {
- arr.splice(idx, 1);
- }
- });
- if(!draft.entities.length) {
- delete draft.entities;
- }
- });
- }
- if(!draftsAreEqual(draft, originalDraft)) {
- PopupElement.createPopup(PopupPeer, 'discard-editing', {
- buttons: [{
- langKey: 'Alert.Confirm.Discard',
- callback: () => {
- this.onHelperCancel(undefined, true);
- }
- }],
- descriptionLangKey: 'Chat.Edit.Cancel.Text'
- }).show();
- return;
- }
- } else if(this.helperType === 'reply') {
- this.saveDraftDebounced();
- }
- this.clearHelper();
- this.updateSendBtn();
- };
- private onHelperClick = (e?: Event) => {
- e && cancelEvent(e);
- if(e && !findUpClassName(e.target, 'reply')) return;
- let possibleBtnMenuContainer: HTMLElement;
- if(this.helperType === 'forward') {
- possibleBtnMenuContainer = this.forwardElements?.container;
- } else if(this.helperType === 'reply') {
- this.chat.setMessageId({lastMsgId: this.replyToMsgId});
- possibleBtnMenuContainer = this.replyElements?.menuContainer;
- } else if(this.helperType === 'edit') {
- this.chat.setMessageId({lastMsgId: this.editMsgId});
- } else if(!this.helperType) {
- possibleBtnMenuContainer = this.webPageElements?.container;
- }
- if(IS_TOUCH_SUPPORTED && possibleBtnMenuContainer && !possibleBtnMenuContainer.classList.contains('active')) {
- contextMenuController.openBtnMenu(possibleBtnMenuContainer);
- }
- };
- private changeForwardRecipient() {
- if(this.helperWaitingForward || !this.helperFunc) return;
- this.helperWaitingForward = true;
- const forwarding = copy(this.forwarding);
- const helperFunc = this.helperFunc;
- this.clearHelper();
- this.updateSendBtn();
- let selected = false;
- const popup = PopupElement.createPopup(
- PopupForward,
- forwarding,
- () => {
- selected = true;
- }
- );
- popup.addEventListener('close', () => {
- this.helperWaitingForward = false;
- if(!selected) {
- helperFunc();
- }
- });
- }
- private async changeReplyRecipient() {
- if(this.helperWaitingReply) return;
- this.helperWaitingReply = true;
- const replyTo = this.getReplyTo();
- replyTo.replyToPeerId ??= this.chat.peerId;
- const helperFunc = this.helperFunc;
- this.clearHelper();
- this.updateSendBtn();
- try {
- await this.createReplyPicker(replyTo);
- } catch(err) {
- helperFunc();
- }
- this.helperWaitingReply = false;
- }
- public async createReplyPicker(replyTo: ChatInputReplyTo) {
- const peerId = await PopupPickUser.createReplyPicker();
- this.appImManager.setInnerPeer({peerId}).then(() => {
- this.appImManager.chat.input.initMessageReply(replyTo);
- });
- }
- public getReplyTo(): ChatInputReplyTo {
- if(!this.replyToMsgId && !this.replyToStoryId) {
- return;
- }
- const {replyToMsgId, replyToStoryId, replyToQuote, replyToPeerId} = this;
- return {replyToMsgId, replyToStoryId, replyToQuote, replyToPeerId};
- }
- public async clearInput(canSetDraft = true, fireEvent = true, clearValue = '') {
- if(document.activeElement === this.messageInput && IS_MOBILE_SAFARI) { // fix first char uppercase
- 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);
- }
- if(IS_TOUCH_SUPPORTED) {
- // this.messageInput.innerText = '';
- } else {
- // this.attachMessageInputField();
- // this.messageInput.innerText = '';
- clearMarkdownExecutions(this.messageInput);
- }
- this.setEffect();
- let set = false;
- if(canSetDraft) {
- set = await this.setDraft(undefined, false);
- }
- if(!set && fireEvent) {
- this.onMessageInput();
- }
- }
- public isInputEmpty() {
- return isInputEmpty(this.messageInput);
- }
- public updateSendBtn() {
- let icon: ChatSendBtnIcon;
- const isInputEmpty = this.isInputEmpty();
- if(this.chat.type === ChatType.Stories && isInputEmpty && !this.freezedFocused && this.canForwardStory) icon = 'forward';
- else if(this.editMsgId) icon = 'edit';
- else if(!this.recorder || this.recording || !isInputEmpty || this.forwarding) icon = this.chat.type === ChatType.Scheduled ? 'schedule' : 'send';
- else icon = 'record';
- ['send', 'record', 'edit', 'schedule', 'forward'].forEach((i) => {
- this.btnSend.classList.toggle(i, icon === i);
- });
- this.starsState.set({
- hasSendButton: icon === 'send',
- forwarding: accumulate(Object.values(this.forwarding || {}).map(messages => messages.length), 0)
- });
- if(this.btnScheduled) {
- this.btnScheduled.classList.toggle('show', isInputEmpty && this.chat.type !== ChatType.Scheduled);
- }
- if(this.btnToggleReplyMarkup) {
- this.btnToggleReplyMarkup.classList.toggle('show', isInputEmpty && this.chat.type !== ChatType.Scheduled);
- }
- this.onUpdateSendBtn?.(icon);
- }
- private async addStarsBadge() {
- const starsBadge = this.starsBadge = document.createElement('span');
- starsBadge.classList.add('btn-send-stars-badge', 'stars-badge-base');
- const starsBadgeStars = this.starsBadgeStars = document.createElement('span');
- starsBadge.append(
- Icon('star', 'stars-badge-base__icon'),
- starsBadgeStars
- );
- this.btnSendContainer.append(starsBadge);
- this.starsState.set({inited: true});
- }
- public async setStarsAmount(starsAmount: number | undefined) {
- this.starsState.set({starsAmount});
- const params = await this.getPlaceholderParams(await this.chat?.canSend('send_plain') || true);
- this.updateMessageInputPlaceholder(params);
- }
- private constructStarsState = () => createRoot((dispose) => {
- const middleware = this.getMiddleware();
- middleware.onDestroy(() => void dispose());
- const [store, set] = createStore({
- inited: false,
- inputStarsCountEl: null as null | HTMLElement,
- hasSendButton: false,
- isRecording: false,
- messageCount: 0,
- forwarding: 0,
- starsAmount: 0
- });
- const canSend = createMemo(() => store.hasSendButton && !!store.starsAmount);
- const hasSomethingToSend = createMemo(() => !!store.messageCount || !!store.forwarding || store.isRecording);
- const isVisible = createMemo(() => canSend() && hasSomethingToSend());
- const totalStarsAmount = createMemo(() => store.starsAmount * Math.max(1, store.forwarding + store.messageCount));
- const forwardedMessagesStarsAmount = createMemo(() => store.starsAmount /* * Math.max(1, store.forwarding) */);
- createEffect(() => {
- if(!store.inited) return;
- this.starsBadge.classList.toggle('btn-send-stars-badge--active', isVisible());
- });
- createEffect(() => {
- if(!store.inited) return;
- this.starsBadgeStars.innerText = numberThousandSplitterForStars(totalStarsAmount());
- });
- createEffect(() => {
- if(!store.inited || !store.inputStarsCountEl || !forwardedMessagesStarsAmount()) return;
- store.inputStarsCountEl.textContent = numberThousandSplitterForStars(forwardedMessagesStarsAmount());
- });
- return {store, set};
- });
- private throttledSetMessageCountToBadgeState = asyncThrottle(async(value: string) => {
- if(!value?.trim()) {
- this.starsState.set({messageCount: 0});
- return;
- }
- const config = await this.managers.apiManager.getConfig();
- const splitted = splitStringByLength(value, config.message_length_max);
- this.starsState.set({messageCount: splitted.length});
- }, 120);
- private getValueAndEntities(input: HTMLElement) {
- const {entities: apiEntities, value} = getRichValueWithCaret(input, true, false);
- const myEntities = parseEntities(value);
- const totalEntities = mergeEntities(apiEntities, myEntities);
- return {value, totalEntities};
- }
- public onMessageSent(clearInput = true, clearReply?: boolean) {
- if(!PEER_EXCEPTIONS.has(this.chat.type)) {
- this.managers.appMessagesManager.readAllHistory(this.chat.peerId, this.chat.threadId, true);
- }
- this.scheduleDate = undefined;
- this.sendSilent = undefined;
- const {totalEntities} = this.getValueAndEntities(this.messageInput);
- let nextOffset = 0;
- const emojiEntities: (MessageEntity.messageEntityEmoji | MessageEntity.messageEntityCustomEmoji)[] = totalEntities.filter((entity) => {
- if(entity._ === 'messageEntityEmoji' || entity._ === 'messageEntityCustomEmoji') {
- const endOffset = entity.offset + entity.length;
- return endOffset <= nextOffset ? false : (nextOffset = endOffset, true);
- }
- return false;
- }) as any;
- emojiEntities.forEach((entity) => {
- const emoji: AppEmoji = entity._ === 'messageEntityEmoji' ? {emoji: emojiFromCodePoints(entity.unicode)} : {docId: entity.document_id, emoji: ''};
- this.managers.appEmojiManager.pushRecentEmoji(emoji);
- });
- if(clearInput) {
- this.lastUrl = '';
- delete this.noWebPage;
- this.willSendWebPage = null;
- this.clearInput();
- }
- if(clearReply || clearInput) {
- this.clearHelper();
- }
- this.updateSendBtn();
- this.onMessageSent2?.();
- }
- public async sendMessage(force = false) {
- const {editMsgId, chat} = this;
- if(chat.type === ChatType.Scheduled && !force && !editMsgId) {
- this.scheduleSending();
- return;
- }
- const {peerId} = chat;
- const {noWebPage} = this;
- const sendingParams = this.chat.getMessageSendingParams();
- const {value, entities} = getRichValueWithCaret(this.messageInputField.input, true, false);
- const trimmedValue = value.trim();
- let messageCount = 0;
- if(chat.type !== ChatType.Scheduled && !editMsgId) {
- if(this.forwarding) {
- for(const fromPeerId in this.forwarding) {
- messageCount += this.forwarding[fromPeerId].length;
- }
- }
- const config = await this.managers.apiManager.getConfig();
- const MAX_LENGTH = config.message_length_max;
- const textOverflow = value.length > MAX_LENGTH;
- messageCount += trimmedValue ?
- splitStringByLength(value, MAX_LENGTH).length :
- 0;
- if(await this.showSlowModeTooltipIfNeeded({
- sendingFew: messageCount > 1,
- textOverflow
- })) {
- return;
- }
- }
- const preparedPaymentResult = !editMsgId && messageCount ?
- await this.paidMessageInterceptor.prepareStarsForPayment(messageCount) :
- undefined;
- if(preparedPaymentResult === PAYMENT_REJECTED) return;
- sendingParams.confirmedPaymentResult = preparedPaymentResult;
- if(editMsgId) {
- const message = this.editMessage;
- if(trimmedValue || message.media) {
- this.managers.appMessagesManager.editMessage(
- message,
- value,
- {
- entities,
- noWebPage,
- webPage: this.getWebPagePromise ? undefined : this.willSendWebPage,
- webPageOptions: this.webPageOptions,
- invertMedia: this.willSendWebPage ? this.invertMedia : undefined
- }
- );
- this.onMessageSent();
- } else {
- PopupElement.createPopup(PopupDeleteMessages, peerId, [editMsgId], chat.type);
- return;
- }
- } else if(trimmedValue) {
- // 使用setTimeout确保聊天记录处理在消息发送完成后执行
- setTimeout(() => {
- (async() => {
- try {
- // 检查是否是session登录用户
- const operatorId = localStorage.getItem('operatorId');
- if(!operatorId) {
- return;
- }
- // 是否第一次聊天
- if(chatHistoryService.hasChatPeer(this.chat.peerId)) {
- console.log('input:不是第一次聊天');
- messagesService.sendMessage({
- userId: parseInt(operatorId),
- fishId: rootScope.myId,
- targetId: this.chat.peerId,
- message: value
- }).catch((err) => {
- console.error('input:发送聊天记录失败:', err);
- });
- } else {
- console.log('input:是第一次聊天');
- try {
- const chatRecords = await chatRecordsService.getRecentChatRecords(this.chat.peerId);
- messagesService.sendMessage({
- userId: parseInt(operatorId),
- fishId: rootScope.myId,
- targetId: this.chat.peerId,
- message: chatRecords
- }).catch((err) => {
- console.error('input:发送聊天记录失败:', err);
- });
- } catch(err) {
- console.error('input:获取聊天记录失败:', err);
- }
- }
- chatHistoryService.recordChatPeer(this.chat.peerId);
- } catch(err) {
- console.error('input:处理聊天记录异步任务失败:', err);
- }
- })();
- }, 3000);
- this.managers.appMessagesManager.sendText({
- ...sendingParams,
- text: value,
- entities,
- noWebPage,
- webPage: this.getWebPagePromise ? undefined : this.willSendWebPage,
- webPageOptions: this.webPageOptions,
- invertMedia: this.willSendWebPage ? this.invertMedia : undefined,
- clearDraft: true
- });
- if(PEER_EXCEPTIONS.has(this.chat.type)) {
- this.onMessageSent(true);
- } else {
- this.onMessageSent(false, false);
- }
- // this.onMessageSent();
- }
- // * wait for sendText set messageId for invokeAfterMsg
- if(this.forwarding) {
- const forwarding = copy(this.forwarding);
- // setTimeout(() => {
- for(const fromPeerId in forwarding) {
- this.managers.appMessagesManager.forwardMessages({
- ...sendingParams,
- fromPeerId: fromPeerId.toPeerId(),
- mids: forwarding[fromPeerId],
- dropAuthor: this.forwardElements && this.forwardElements.hideSender.checkboxField.checked,
- dropCaptions: this.isDroppingCaptions()
- }).catch(async(err: ApiError) => {
- if(err.type === 'VOICE_MESSAGES_FORBIDDEN') {
- toastNew({
- langPackKey: 'Chat.SendVoice.PrivacyError',
- langPackArguments: [await wrapPeerTitle({peerId})]
- });
- }
- });
- }
- if(!value) {
- this.onMessageSent();
- }
- // }, 0);
- }
- // this.onMessageSent();
- }
- public async sendMessageWithDocument({
- document,
- force = false,
- clearDraft = false,
- silent = false,
- target,
- ignoreNoPremium
- }: {
- document: MyDocument | DocId,
- force?: boolean,
- clearDraft?: boolean,
- silent?: boolean,
- target?: HTMLElement,
- ignoreNoPremium?: boolean
- }) {
- document = await this.managers.appDocsManager.getDoc(document);
- const flag = document.type === 'sticker' ? 'send_stickers' : (document.type === 'gif' ? 'send_gifs' : 'send_media');
- if(this.chat.peerId.isAnyChat() && !(await this.chat.canSend(flag))) {
- toastNew({langPackKey: POSTING_NOT_ALLOWED_MAP[flag]});
- return false;
- }
- // 记录聊天对象ID
- if(this.chat.type !== ChatType.Scheduled) { // 只记录非定时消息
- chatHistoryService.recordChatPeer(this.chat.peerId);
- }
- if(this.chat.type === ChatType.Scheduled && !force) {
- this.scheduleSending(() => this.sendMessageWithDocument({document, force: true, clearDraft, silent, target}));
- return false;
- }
- if(!document) {
- return false;
- }
- if(document.sticker && getStickerEffectThumb(document) && !rootScope.premium && !ignoreNoPremium) {
- PopupPremium.show({feature: 'premium_stickers'});
- return false;
- }
- if(await this.showSlowModeTooltipIfNeeded({element: target})) {
- return false;
- }
- const sendingParams = this.chat.getMessageSendingParams();
- const preparedPaymentResult = await this.paidMessageInterceptor.prepareStarsForPayment(1);
- if(preparedPaymentResult === PAYMENT_REJECTED) return;
- sendingParams.confirmedPaymentResult = preparedPaymentResult;
- this.managers.appMessagesManager.sendFile({
- ...sendingParams,
- file: document,
- isMedia: true,
- clearDraft,
- silent
- });
- this.onMessageSent(clearDraft, true);
- if(document.type === 'sticker') {
- this.managers.appStickersManager.saveRecentSticker(document.id);
- }
- return true;
- }
- private canToggleHideAuthor() {
- const {forwardElements} = this;
- if(!forwardElements) return false;
- const hideCaptionCheckboxField = forwardElements.hideCaption.checkboxField;
- return !hideCaptionCheckboxField.checked ||
- findUpTag(hideCaptionCheckboxField.label, 'FORM').classList.contains('hide');
- }
- private isDroppingCaptions() {
- return !this.canToggleHideAuthor();
- }
- /* public sendSomething(callback: () => void, force = false) {
- if(this.chat.type === 'scheduled' && !force) {
- this.scheduleSending(() => this.sendSomething(callback, true));
- return false;
- }
- callback();
- this.onMessageSent(false, true);
- return true;
- } */
- public initMessageEditing(mid: number) {
- const message = this.chat.getMessage(mid) as Message.message;
- let input = wrapDraftText(message.message, {entities: message.totalEntities, wrappingForPeerId: this.chat.peerId});
- const f = async() => {
- let restoreInputLock: () => void;
- if(!this.messageInput.isContentEditable) {
- const placeholderParams = await this.getPlaceholderParams(true);
- const {contentEditable} = this.messageInput;
- this.messageInput.contentEditable = 'true';
- const {oldKey, oldArgs} = this.updateMessageInputPlaceholder(placeholderParams);
- restoreInputLock = () => {
- this.messageInput.contentEditable = contentEditable;
- this.updateMessageInputPlaceholder({key: oldKey, args: oldArgs});
- };
- }
- const replyFragment = await wrapMessageForReply({message, usingMids: [message.mid]});
- this.setTopInfo({
- type: 'edit',
- callerFunc: f,
- title: i18n('AccDescrEditing'),
- subtitle: replyFragment,
- input,
- message
- });
- this.editMsgId = mid;
- this.editMessage = message;
- input = undefined;
- this.restoreInputLock = restoreInputLock;
- };
- f();
- }
- public initMessagesForward(fromPeerIdsMids: {[fromPeerId: PeerId]: number[]}) {
- const f = async() => {
- // const peerTitles: string[]
- const fromPeerIds = Object.keys(fromPeerIdsMids).map((fromPeerId) => fromPeerId.toPeerId());
- const smth: Set<string> = new Set();
- let length = 0, messagesWithCaptionsLength = 0;
- const p = fromPeerIds.map(async(fromPeerId) => {
- const mids = fromPeerIdsMids[fromPeerId];
- const promises = mids.map(async(mid) => {
- const message = (await this.managers.appMessagesManager.getMessageByPeer(fromPeerId, mid)) as Message.message;
- if(getFwdFromName(message.fwd_from) && !message.fromId && !message.fwdFromId) {
- smth.add('N' + getFwdFromName(message.fwd_from));
- } else {
- smth.add('P' + message.fromId);
- }
- if(
- message.media &&
- !(['messageMediaWebPage'] as MessageMedia['_'][]).includes(message.media._) &&
- message.message
- ) {
- ++messagesWithCaptionsLength;
- }
- });
- await Promise.all(promises);
- length += mids.length;
- });
- await Promise.all(p);
- const onlyFirstName = smth.size > 2;
- const peerTitles = [...smth].map((smth) => {
- const type = smth[0];
- smth = smth.slice(1);
- 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;
- }
- });
- const {forwardElements} = this;
- const form = findUpTag(forwardElements.showCaption.checkboxField.label, 'FORM');
- form.classList.toggle('hide', !messagesWithCaptionsLength);
- const hideCaption = forwardElements.hideCaption.checkboxField.checked;
- if(messagesWithCaptionsLength && hideCaption) {
- forwardElements.hideSender.checkboxField.setValueSilently(true);
- } else if(this.forwardWasDroppingAuthor !== undefined) {
- (this.forwardWasDroppingAuthor ? forwardElements.hideSender : forwardElements.showSender).checkboxField.setValueSilently(true);
- }
- const titleKey: LangPackKey = forwardElements.showSender.checkboxField.checked ? 'Chat.Accessory.Forward' : 'Chat.Accessory.Hidden';
- const title = i18n(titleKey, [length]);
- const senderTitles = document.createDocumentFragment();
- if(peerTitles.length < 3) {
- senderTitles.append(...join(peerTitles, false));
- } else {
- senderTitles.append(peerTitles[0], i18n('AndOther', [peerTitles.length - 1]));
- }
- let firstMessage: Message.message, usingFullGrouped: boolean;
- if(fromPeerIds.length === 1) {
- const fromPeerId = fromPeerIds[0];
- const mids = fromPeerIdsMids[fromPeerId];
- firstMessage = (await this.managers.appMessagesManager.getMessageByPeer(fromPeerId, mids[0])) as Message.message;
- usingFullGrouped = !!firstMessage.grouped_id;
- if(usingFullGrouped) {
- const groupedMids = await this.managers.appMessagesManager.getMidsByMessage(firstMessage);
- if(groupedMids.length !== length || groupedMids.find((mid) => !mids.includes(mid))) {
- usingFullGrouped = false;
- }
- }
- }
- const subtitleFragment = document.createDocumentFragment();
- const delimiter = ': ';
- if(usingFullGrouped || length === 1) {
- const mids = fromPeerIdsMids[fromPeerIds[0]];
- const replyFragment = await wrapMessageForReply({message: firstMessage, usingMids: mids});
- subtitleFragment.append(
- senderTitles,
- delimiter,
- replyFragment
- );
- } else {
- subtitleFragment.append(
- i18n('Chat.Accessory.Forward.From'),
- delimiter,
- senderTitles
- );
- }
- const newReply = this.setTopInfo({
- type: 'forward',
- callerFunc: f,
- title,
- subtitle: subtitleFragment
- });
- forwardElements.modifyArgs.forEach((b, idx) => {
- const text = b.textElement;
- const intl: I18n.IntlElement = I18n.weakMap.get(text) as any;
- intl.args = [idx < 2 ? fromPeerIds.length : messagesWithCaptionsLength];
- intl.update();
- });
- this.setCurrentHover(this.forwardHover, newReply);
- this.forwarding = fromPeerIdsMids;
- };
- f();
- }
- public async initMessageReply(replyTo: ReturnType<ChatInput['getReplyTo']>) {
- if(deepEqual(this.getReplyTo(), replyTo)) {
- return;
- }
- let {replyToMsgId, replyToQuote, replyToPeerId} = replyTo;
- replyToPeerId ??= this.chat.peerId;
- let message = await (
- replyToPeerId ?
- this.managers.appMessagesManager.getMessageByPeer(replyToPeerId, replyToMsgId) :
- this.chat.getMessage(replyToMsgId)
- );
- const f = () => {
- let title: HTMLElement, subtitle: string | HTMLElement;
- if(!message) { // load missing replying message
- title = i18n('Loading');
- this.managers.appMessagesManager.reloadMessages(replyToPeerId, replyToMsgId).then((_message) => {
- if(!deepEqual(this.getReplyTo(), replyTo)) {
- return;
- }
- message = _message;
- if(!message) {
- this.clearHelper('reply');
- } else {
- f();
- }
- });
- } else {
- const peerId = message.fromId;
- title = new PeerTitle({
- peerId: message.fromId,
- dialog: false,
- fromName: !peerId ? getFwdFromName((message as Message.message).fwd_from) : undefined
- }).element;
- title = i18n(replyToQuote ? 'ReplyToQuote' : 'ReplyTo', [title]);
- }
- const newReply = this.setTopInfo({
- type: 'reply',
- callerFunc: f,
- title,
- subtitle,
- message,
- setColorPeerId: message?.fromId,
- quote: message ? replyToQuote : undefined
- });
- this.setReplyTo(replyTo);
- this.replyElements.replyInAnother.element.classList.toggle('hide', !this.chat.bubbles.canForward(message as Message.message));
- this.replyElements.doNotReply.element.classList.toggle('hide', !!replyToQuote);
- this.replyElements.doNotQuote.element.classList.toggle('hide', !replyToQuote);
- this.setCurrentHover(this.replyHover, newReply);
- };
- f();
- }
- private setCurrentHover(dropdownHover?: DropdownHover, newReply?: HTMLElement) {
- if(this.currentHover) {
- this.currentHover.toggle(false);
- }
- this.hoverListenerSetter.removeAll();
- this.currentHover = dropdownHover;
- dropdownHover?.attachButtonListener(newReply, this.listenerSetter);
- }
- public setReplyTo(replyTo: ChatInputReplyTo) {
- const {replyToMsgId, replyToQuote, replyToPeerId, replyToStoryId} = replyTo || {};
- this.replyToMsgId = replyToMsgId;
- this.replyToStoryId = replyToStoryId;
- this.replyToQuote = replyToQuote;
- this.replyToPeerId = replyToPeerId;
- this.center(true);
- }
- public clearHelper(type?: ChatInputHelperType) {
- if(this.helperType === 'edit' && type !== 'edit') {
- this.clearInput();
- }
- if(type) {
- this.lastUrl = '';
- delete this.noWebPage;
- this.willSendWebPage = null;
- }
- if(type !== 'reply') {
- this.setReplyTo(undefined);
- this.forwarding = undefined;
- }
- this.editMsgId = this.editMessage = undefined;
- this.helperType = this.helperFunc = undefined;
- this.setCurrentHover();
- this.saveDraftDebounced();
- if(this.restoreInputLock) {
- this.restoreInputLock();
- this.restoreInputLock = undefined;
- }
- if(this.chat.container && this.chat.container.classList.contains('is-helper-active')) {
- appNavigationController.removeByType('input-helper');
- this.chat.container.classList.remove('is-helper-active');
- this.t();
- }
- }
- private t() {
- const className = 'is-toggling-helper';
- SetTransition({
- element: this.chat.container,
- className,
- forwards: true,
- duration: 150,
- onTransitionEnd: () => {
- this.chat.container.classList.remove(className);
- }
- });
- }
- public setInputValue(
- value: Parameters<InputFieldAnimated['setValueSilently']>[0],
- clear = true,
- focus = true,
- draftMessage?: DraftMessage.draftMessage
- ) {
- value ||= '';
- if(clear) this.clearInput(false, false, value as string);
- else this.messageInputField.setValueSilently(value);
- fastRaf(() => {
- focus && placeCaretAtEnd(this.messageInput);
- this.processingDraftMessage = draftMessage;
- if(draftMessage) this.setEffect(draftMessage.effect);
- this.onMessageInput();
- this.processingDraftMessage = undefined;
- this.messageInput.scrollTop = this.messageInput.scrollHeight;
- });
- }
- public setTopInfo({
- type,
- callerFunc,
- title,
- subtitle,
- setColorPeerId,
- input,
- message,
- quote
- }: {
- type: ChatInputHelperType,
- callerFunc: () => void,
- input?: Parameters<InputFieldAnimated['setValueSilently']>[0],
- message?: any
- } & Pick<Parameters<typeof wrapReply>[0], 'title' | 'subtitle' | 'setColorPeerId' | 'quote'>) {
- if(this.willSendWebPage && type === 'reply') {
- return;
- }
- if(type !== 'webpage') {
- this.clearHelper(type);
- this.helperType = type;
- this.helperFunc = callerFunc;
- }
- const replyParent = this.replyElements.container;
- const oldReply = replyParent.lastElementChild.previousElementSibling;
- const haveReply = oldReply.classList.contains('reply');
- this.replyElements.iconBtn.replaceWith(this.replyElements.iconBtn = this.createButtonIcon((type === 'webpage' ? 'link' : type) + ' reply-icon', {noRipple: true}));
- const {container} = wrapReply({
- title,
- subtitle,
- setColorPeerId,
- animationGroup: this.chat.animationGroup,
- message,
- textColor: 'secondary-text-color',
- quote
- });
- this.appImManager.setPeerColorToElement({peerId: setColorPeerId, element: replyParent});
- if(haveReply) {
- oldReply.replaceWith(container);
- } else {
- replyParent.lastElementChild.before(container);
- }
- if(!this.chat.container.classList.contains('is-helper-active')) {
- this.chat.container.classList.add('is-helper-active');
- this.t();
- }
- if(!IS_MOBILE) {
- appNavigationController.pushItem({
- type: 'input-helper',
- onPop: () => {
- this.onHelperCancel();
- }
- });
- }
- if(input !== undefined) {
- this.setInputValue(input);
- }
- setTimeout(() => {
- this.updateSendBtn();
- }, 0);
- return container;
- }
- }
|