From 38780910dd4d7cf55bebe9b0978b0708f2a061d2 Mon Sep 17 00:00:00 2001
From: morethanwords <thanwords24@gmail.com>
Date: Sat, 9 Jan 2021 18:08:26 +0400
Subject: [PATCH] Draft messages

---
 src/components/chat/bubbles.ts               |  61 +++---
 src/components/chat/chat.ts                  |  44 +++--
 src/components/chat/input.ts                 |  97 ++++++++--
 src/components/chat/pinnedMessage.ts         |   2 +-
 src/components/chat/topbar.ts                |   2 +-
 src/components/popups/newMedia.ts            |   9 +-
 src/components/scrollable.ts                 |  22 ++-
 src/helpers/fastSmoothScroll.ts              |  67 +++++++
 src/layer.d.ts                               |  10 +-
 src/lib/appManagers/apiUpdatesManager.ts     |  22 ++-
 src/lib/appManagers/appDialogsManager.ts     |  38 +++-
 src/lib/appManagers/appDraftsManager.ts      | 187 +++++++++++++++++++
 src/lib/appManagers/appImManager.ts          |  10 +-
 src/lib/appManagers/appMessagesManager.ts    | 137 ++++++++------
 src/lib/appManagers/appStateManager.ts       |   7 +-
 src/lib/langPack.ts                          |   2 +-
 src/lib/mtproto/mtproto.worker.ts            |   4 +-
 src/lib/mtproto/mtprotoworker.ts             |   6 +-
 src/lib/mtproto/networker.ts                 |   4 +-
 src/lib/mtproto/networkerFactory.ts          |   4 +-
 src/lib/rootScope.ts                         |   7 +-
 src/lib/storages/dialogs.ts                  |  39 ++--
 src/scripts/generate_mtproto_types.js        |   4 +-
 src/scripts/in/schema_additional_params.json |  12 +-
 24 files changed, 612 insertions(+), 185 deletions(-)
 create mode 100644 src/lib/appManagers/appDraftsManager.ts

diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts
index 1192b176..44141a60 100644
--- a/src/components/chat/bubbles.ts
+++ b/src/components/chat/bubbles.ts
@@ -383,7 +383,7 @@ export default class ChatBubbles {
       let details = e;
 
       if(!this.scrolledAllDown) {
-        this.chat.setPeer(this.peerId, 0);
+        this.chat.setMessageId(0);
       } else {
         this.renderNewMessagesByIds([details.messageId], true);
       }
@@ -729,11 +729,11 @@ export default class ChatBubbles {
         this.chat.appImManager.setInnerPeer(replyToPeerId, replyToMid, this.chat.type, this.chat.threadId);
 
         /* if(this.chat.type === 'discussion') {
-          this.chat.appImManager.setPeer(this.peerId, originalMessageId);
+          this.chat.appImManager.setMessageId(, originalMessageId);
         } else {
           this.chat.appImManager.setInnerPeer(this.peerId, originalMessageId);
         } */
-        //this.chat.setPeer(this.peerId, originalMessageId);
+        //this.chat.setMessageId(, originalMessageId);
       }
     } else if(target.tagName == 'IMG' && target.parentElement.tagName == "AVATAR-ELEMENT") {
       let peerId = +target.parentElement.getAttribute('peer');
@@ -769,9 +769,9 @@ export default class ChatBubbles {
       this.replyFollowHistory.sort((a, b) => b - a);
 
       const mid = this.replyFollowHistory.pop();
-      this.chat.setPeer(this.peerId, mid);
+      this.chat.setMessageId(mid);
     } else {
-      this.chat.setPeer(this.peerId/* , dialog.top_message */);
+      this.chat.setMessageId(/* , dialog.top_message */);
       // const dialog = this.appMessagesManager.getDialogByPeerId(this.peerId)[0];
       
       // if(dialog) {
@@ -1076,7 +1076,7 @@ export default class ChatBubbles {
       } else {
         str = months[date.getMonth()] + ' ' + date.getDate();
 
-        if(date.getFullYear() != today.getFullYear()) {
+        if(date.getFullYear() !== today.getFullYear()) {
           str += ', ' + date.getFullYear();
         }
       }
@@ -1156,9 +1156,7 @@ export default class ChatBubbles {
     this.bubbleGroups.cleanup();
     this.unreadOut.clear();
     this.needUpdate.length = 0;
-    //this.lazyLoadQueue.clear();
-    
-    //this.chatInputC.replyElements.cancelBtn.click();
+    this.lazyLoadQueue.clear();
 
     // clear messages
     if(bubblesToo) {
@@ -1194,13 +1192,13 @@ export default class ChatBubbles {
     //console.time('appImManager setPeer');
     //console.time('appImManager setPeer pre promise');
     ////console.time('appImManager: pre render start');
-    if(peerId == 0) {
+    if(!peerId) {
       this.cleanup(true);
       this.peerId = 0;
       return null;
     }
 
-    const samePeer = this.peerId == peerId;
+    const samePeer = this.peerId === peerId;
 
     const historyStorage = this.appMessagesManager.getHistoryStorage(peerId, this.chat.threadId);
     let topMessage = this.chat.type === 'pinned' ? this.appMessagesManager.pinnedMessages[peerId].maxId : historyStorage.maxId ?? 0;
@@ -1248,7 +1246,9 @@ export default class ChatBubbles {
       this.replyFollowHistory.length = 0;
     }
 
-    this.log('setPeer peerId:', this.peerId, historyStorage, lastMsgId, topMessage);
+    if(DEBUG) {
+      this.log('setPeer peerId:', this.peerId, historyStorage, lastMsgId, topMessage);
+    }
 
     // add last message, bc in getHistory will load < max_id
     const additionMsgId = isJump || this.chat.type === 'scheduled' ? 0 : topMessage;
@@ -1261,7 +1261,7 @@ export default class ChatBubbles {
 
     let maxBubbleId = 0;
     if(samePeer) {
-      let el = getElementByPoint(this.chat.bubbles.scrollable.container, 'bottom');
+      let el = getElementByPoint(this.chat.bubbles.scrollable.container, 'bottom'); // ! this may not work if being called when chat is hidden
       //this.chat.log('[PM]: setCorrectIndex: get last element perf:', performance.now() - perf, el);
       if(el) {
         el = findUpClassName(el, 'bubble');
@@ -1281,10 +1281,6 @@ export default class ChatBubbles {
     this.chatInner.className = oldChatInner.className;
     this.chatInner.classList.add('disable-hover', 'is-scrolling');
 
-    if(!samePeer) {
-      this.lazyLoadQueue.clear();
-    }
-
     this.lazyLoadQueue.lock();
 
     const {promise, cached} = this.getHistory(lastMsgId, true, isJump, additionMsgId);
@@ -1321,8 +1317,6 @@ export default class ChatBubbles {
       this.scrollable.container.append(this.chatInner);
       animationIntersector.unlockGroup(CHAT_ANIMATION_GROUP);
       animationIntersector.checkAnimations(false, CHAT_ANIMATION_GROUP/* , true */);
-      //this.scrollable.attachSentinels();
-      //this.scrollable.container.insertBefore(this.chatInner, this.scrollable.container.lastElementChild);
 
       this.lazyLoadQueue.unlock();
 
@@ -1457,7 +1451,6 @@ export default class ChatBubbles {
 
     this.messagesQueuePromise = new Promise((resolve, reject) => {
       setTimeout(() => {
-        const chatInner = this.chatInner;
         const queue = this.messagesQueue.slice();
         this.messagesQueue.length = 0;
 
@@ -1468,10 +1461,10 @@ export default class ChatBubbles {
         // promises.push(getHeavyAnimationPromise());
 
         //this.log('promises to call', promises, queue);
+        const middleware = this.getMiddleware();
         Promise.all(promises).then(() => {
-          if(this.chatInner != chatInner) {
-            //this.log.warn('chatInner changed!', this.chatInner, chatInner);
-            return reject('chatInner changed!');
+          if(!middleware()) {
+            return Promise.reject('setMessagesQueuePromise: peer changed!');
           }
 
           if(this.messagesQueueOnRender) {
@@ -1490,7 +1483,7 @@ export default class ChatBubbles {
           if(this.messagesQueue.length) {
             this.setMessagesQueuePromise();
           }
-        }, reject);
+        }).catch(reject);
       }, 0);
     });
   }
@@ -2333,6 +2326,7 @@ export default class ChatBubbles {
       //const realLength = this.scrollable.container.childElementCount;
       let previousScrollHeightMinusTop: number/* , previousScrollHeight: number */;
       //if(realLength > 0/*  && (reverse || isSafari) */) { // for safari need set when scrolling bottom too
+      //if(!this.scrollable.isHeavyScrolling) {
         this.messagesQueueOnRender = () => {
           const {scrollTop, scrollHeight} = this.scrollable;
 
@@ -2351,6 +2345,7 @@ export default class ChatBubbles {
           this.messagesQueueOnRender = undefined;
         };
       //}
+      //}
 
       while(history.length) {
         let message = this.chat.getMessage(method());
@@ -2412,7 +2407,7 @@ export default class ChatBubbles {
         return;
       }
 
-      this.chat.setPeer(this.peerId, (history.messages[0] as MyMessage).mid);
+      this.chat.setMessageId((history.messages[0] as MyMessage).mid);
       //console.log('got history date:', history);
     });
   };
@@ -2540,17 +2535,17 @@ export default class ChatBubbles {
       const promise = result.then((result) => {
         //this.log('getHistory not cached result by maxId:', maxId, reverse, isBackLimit, result, peerId, justLoad);
 
+        if(reverse ? this.getHistoryTopPromise !== promise : this.getHistoryBottomPromise !== promise) {
+          this.log.warn('getHistory: peer changed');
+          ////console.timeEnd('render history total');
+          return Promise.reject();
+        }
+
         if(justLoad) {
           this.scrollable.onScroll(); // нужно делать из-за ранней прогрузки
           return true;
         }
         //console.timeEnd('appImManager call getHistory');
-        
-        if(this.peerId != peerId || (this.getHistoryTopPromise != promise && this.getHistoryBottomPromise != promise)) {
-          this.log.warn('peer changed');
-          ////console.timeEnd('render history total');
-          return Promise.reject();
-        }
 
         processResult(result);
         
@@ -2561,7 +2556,7 @@ export default class ChatBubbles {
         });
       }, (err) => {
         this.log.error('getHistory error:', err);
-        return false;
+        throw err;
       });
       
       return promise;
@@ -2626,7 +2621,7 @@ export default class ChatBubbles {
     }
 
     (reverse ? this.getHistoryTopPromise = waitPromise : this.getHistoryBottomPromise = waitPromise);
-    waitPromise.finally(() => {
+    waitPromise.then(() => {
       (reverse ? this.getHistoryTopPromise = undefined : this.getHistoryBottomPromise = undefined);
     });
 
diff --git a/src/components/chat/chat.ts b/src/components/chat/chat.ts
index 58d7b706..c2b77b8f 100644
--- a/src/components/chat/chat.ts
+++ b/src/components/chat/chat.ts
@@ -11,6 +11,8 @@ import type { AppStickersManager } from "../../lib/appManagers/appStickersManage
 import type { AppUsersManager } from "../../lib/appManagers/appUsersManager";
 import type { AppWebPagesManager } from "../../lib/appManagers/appWebPagesManager";
 import type { ApiManagerProxy } from "../../lib/mtproto/mtprotoworker";
+import type { AppDraftsManager } from "../../lib/appManagers/appDraftsManager";
+import type { ServerTimeManager } from "../../lib/mtproto/serverTimeManager";
 import EventListenerBase from "../../helpers/eventListenerBase";
 import { logger, LogLevels } from "../../lib/logger";
 import rootScope from "../../lib/rootScope";
@@ -36,6 +38,7 @@ export default class Chat extends EventListenerBase<{
   public selection: ChatSelection;
   public contextMenu: ChatContextMenu;
 
+  public initPeerId = 0;
   public peerId = 0;
   public threadId: number;
   public setPeerPromise: Promise<void>;
@@ -45,7 +48,7 @@ export default class Chat extends EventListenerBase<{
 
   public type: ChatType = 'chat';
   
-  constructor(public appImManager: AppImManager, public appChatsManager: AppChatsManager, public appDocsManager: AppDocsManager, public appInlineBotsManager: AppInlineBotsManager, public appMessagesManager: AppMessagesManager, public appPeersManager: AppPeersManager, public appPhotosManager: AppPhotosManager, public appProfileManager: AppProfileManager, public appStickersManager: AppStickersManager, public appUsersManager: AppUsersManager, public appWebPagesManager: AppWebPagesManager, public appPollsManager: AppPollsManager, public apiManager: ApiManagerProxy) {
+  constructor(public appImManager: AppImManager, public appChatsManager: AppChatsManager, public appDocsManager: AppDocsManager, public appInlineBotsManager: AppInlineBotsManager, public appMessagesManager: AppMessagesManager, public appPeersManager: AppPeersManager, public appPhotosManager: AppPhotosManager, public appProfileManager: AppProfileManager, public appStickersManager: AppStickersManager, public appUsersManager: AppUsersManager, public appWebPagesManager: AppWebPagesManager, public appPollsManager: AppPollsManager, public apiManager: ApiManagerProxy, public appDraftsManager: AppDraftsManager, public serverTimeManager: ServerTimeManager) {
     super();
 
     this.container = document.createElement('div');
@@ -72,10 +75,12 @@ export default class Chat extends EventListenerBase<{
     }
   }
 
-  public init() {
+  public init(peerId: number) {
+    this.initPeerId = peerId;
+
     this.topbar = new ChatTopbar(this, appSidebarRight, this.appMessagesManager, this.appPeersManager, this.appChatsManager);
     this.bubbles = new ChatBubbles(this, this.appMessagesManager, this.appStickersManager, this.appUsersManager, this.appInlineBotsManager, this.appPhotosManager, this.appDocsManager, this.appPeersManager, this.appChatsManager);
-    this.input = new ChatInput(this, this.appMessagesManager, this.appDocsManager, this.appChatsManager, this.appPeersManager, this.appWebPagesManager, this.appImManager);
+    this.input = new ChatInput(this, this.appMessagesManager, this.appDocsManager, this.appChatsManager, this.appPeersManager, this.appWebPagesManager, this.appImManager, this.appDraftsManager, this.serverTimeManager);
     this.selection = new ChatSelection(this, this.bubbles, this.input, this.appMessagesManager);
     this.contextMenu = new ChatContextMenu(this.bubbles.bubblesContainer, this, this.appMessagesManager, this.appChatsManager, this.appPeersManager, this.appPollsManager);
 
@@ -131,22 +136,25 @@ export default class Chat extends EventListenerBase<{
   public cleanup() {
     this.input.cleanup();
     this.selection.cleanup();
-
-    this.peerChanged = false;
   }
 
   public setPeer(peerId: number, lastMsgId?: number) {
     if(this.init) {
-      this.init();
+      this.init(peerId);
       this.init = null;
     }
 
+    const samePeer = this.peerId === peerId;
+    if(!samePeer) {
+      rootScope.broadcast('peer_changing', this);
+      this.peerId = peerId;
+    }
+
     //console.time('appImManager setPeer');
     //console.time('appImManager setPeer pre promise');
     ////console.time('appImManager: pre render start');
-    if(peerId == 0) {
+    if(!peerId) {
       appSidebarRight.toggleSidebar(false);
-      this.peerId = peerId;
       this.cleanup();
       this.topbar.setPeer(peerId);
       this.bubbles.setPeer(peerId);
@@ -155,21 +163,17 @@ export default class Chat extends EventListenerBase<{
       return;
     }
 
-    const samePeer = this.peerId == peerId;
-
     // set new
     if(!samePeer) {
-      if(appSidebarRight.historyTabIds[appSidebarRight.historyTabIds.length - 1] == AppSidebarRight.SLIDERITEMSIDS.search) {
+      if(appSidebarRight.historyTabIds[appSidebarRight.historyTabIds.length - 1] === AppSidebarRight.SLIDERITEMSIDS.search) {
         appSidebarRight.closeTab(AppSidebarRight.SLIDERITEMSIDS.search);
       }
 
-      this.peerId = peerId;
       appSidebarRight.sharedMediaTab.setPeer(peerId, this.threadId);
-      this.cleanup();
-    } else {
-      this.peerChanged = true;
     }
 
+    this.peerChanged = samePeer;
+
     const result = this.bubbles.setPeer(peerId, lastMsgId);
     if(!result) {
       return;
@@ -179,8 +183,8 @@ export default class Chat extends EventListenerBase<{
 
     //console.timeEnd('appImManager setPeer pre promise');
     
-    this.setPeerPromise = promise.finally(() => {
-      if(this.peerId == peerId) {
+    const setPeerPromise = this.setPeerPromise = promise.finally(() => {
+      if(this.setPeerPromise === setPeerPromise) {
         this.setPeerPromise = null;
       }
     });
@@ -194,12 +198,18 @@ export default class Chat extends EventListenerBase<{
     return result;
   }
 
+  public setMessageId(messageId?: number) {
+    return this.setPeer(this.peerId, messageId);
+  }
+
   public finishPeerChange(isTarget: boolean, isJump: boolean, lastMsgId: number) {
     if(this.peerChanged) return;
 
     let peerId = this.peerId;
     this.peerChanged = true;
 
+    this.cleanup();
+
     this.topbar.setPeer(peerId);
     this.topbar.finishPeerChange(isTarget, isJump, lastMsgId);
     this.bubbles.finishPeerChange();
diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts
index 96465ccc..b6a32398 100644
--- a/src/components/chat/input.ts
+++ b/src/components/chat/input.ts
@@ -4,6 +4,8 @@ import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManage
 import type { AppPeersManager } from '../../lib/appManagers/appPeersManager';
 import type { AppWebPagesManager } from "../../lib/appManagers/appWebPagesManager";
 import type { AppImManager } from '../../lib/appManagers/appImManager';
+import type { AppDraftsManager, MyDraftMessage } from '../../lib/appManagers/appDraftsManager';
+import type { ServerTimeManager } from '../../lib/mtproto/serverTimeManager';
 import type Chat from './chat';
 import Recorder from '../../../public/recorder.min';
 import { isTouchSupported } from "../../helpers/touchSupport";
@@ -21,7 +23,7 @@ import Scrollable from "../scrollable";
 import { toast } from "../toast";
 import { wrapReply } from "../wrappers";
 import InputField from '../inputField';
-import { MessageEntity } from '../../layer';
+import { MessageEntity, DraftMessage } from '../../layer';
 import StickersHelper from './stickersHelper';
 import ButtonIcon from '../buttonIcon';
 import DivAndCaption from '../divAndCaption';
@@ -32,7 +34,8 @@ import PopupSchedule from '../popups/schedule';
 import SendMenu from './sendContextMenu';
 import rootScope from '../../lib/rootScope';
 import PopupPinMessage from '../popups/unpinMessage';
-import { isApple } from '../../helpers/userAgent';
+import { debounce } from '../../helpers/schedulers';
+import { tsNow } from '../../helpers/date';
 
 const RECORD_MIN_TIME = 500;
 const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@@ -109,9 +112,11 @@ export default class ChatInput {
 
   public goDownBtn: HTMLButtonElement;
   public goDownUnreadBadge: HTMLElement;
-  btnScheduled: HTMLButtonElement;
+  public btnScheduled: HTMLButtonElement;
 
-  constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appDocsManager: AppDocsManager, private appChatsManager: AppChatsManager, private appPeersManager: AppPeersManager, private appWebPagesManager: AppWebPagesManager, private appImManager: AppImManager) {
+  public saveDraftDebounced: () => void;
+
+  constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appDocsManager: AppDocsManager, private appChatsManager: AppChatsManager, private appPeersManager: AppPeersManager, private appWebPagesManager: AppWebPagesManager, private appImManager: AppImManager, private appDraftsManager: AppDraftsManager, private serverTimeManager: ServerTimeManager) {
     this.listenerSetter = new ListenerSetter();
   }
 
@@ -311,6 +316,18 @@ export default class ChatInput {
       }
     });
 
+    this.listenerSetter.add(rootScope, 'draft_updated', (e) => {
+      const {peerId, threadId, draft} = e;
+      if(this.chat.threadId !== threadId || this.chat.peerId !== peerId) return;
+      this.setDraft(draft);
+    });
+
+    this.listenerSetter.add(rootScope, 'peer_changing', (chat) => {
+      if(this.chat === chat) {
+        this.saveDraft();
+      }
+    });
+
     try {
       this.recorder = new Recorder({
         //encoderBitRate: 32,
@@ -388,7 +405,8 @@ export default class ChatInput {
             waveform: result.waveform,
             objectURL: result.url,
             replyToMsgId: this.replyToMsgId,
-            threadId: this.chat.threadId
+            threadId: this.chat.threadId,
+            clearDraft: true
           });
 
           this.onMessageSent(false, true);
@@ -398,6 +416,8 @@ export default class ChatInput {
 
     attachClickEvent(this.replyElements.cancelBtn, this.onHelperCancel, {listenerSetter: this.listenerSetter});
     attachClickEvent(this.replyElements.container, this.onHelperClick, {listenerSetter: this.listenerSetter});
+
+    this.saveDraftDebounced = debounce(() => this.saveDraft(), 2500, false, true);
   }
 
   public constructPinnedHelpers() {
@@ -470,6 +490,29 @@ export default class ChatInput {
     this.goDownUnreadBadge.classList.toggle('badge-gray', this.appMessagesManager.isPeerMuted(this.chat.peerId));
   }
 
+  public saveDraft() {
+    if(!this.chat.peerId) return;
+    
+    const entities: MessageEntity[] = [];
+    const str = getRichValue(this.messageInputField.input, entities);
+
+    let draft: DraftMessage.draftMessage;
+    if(str.length) {
+      draft = {
+        _: 'draftMessage',
+        date: tsNow(true) + this.serverTimeManager.serverTimeOffset,
+        message: str,
+        entities: entities.length ? entities : undefined,
+        pFlags: {
+          no_webpage: this.noWebPage
+        },
+        reply_to_msg_id: this.replyToMsgId
+      };
+    }
+
+    this.appDraftsManager.syncDraft(this.chat.peerId, this.chat.threadId, draft);
+  }
+
   public destroy() {
     //this.chat.log.error('Input destroying');
 
@@ -493,6 +536,20 @@ export default class ChatInput {
     }
   }
 
+  public setDraft(draft?: MyDraftMessage, fromUpdate = true) {
+    if(!isInputEmpty(this.messageInput)) return;
+    
+    if(!draft) {
+      draft = this.appDraftsManager.getDraft(this.chat.peerId, this.chat.threadId);
+
+      if(!draft) {
+        return;
+      }
+    }
+
+    this.setInputValue(draft.rMessage, fromUpdate, fromUpdate);
+  }
+
   public finishPeerChange() {
     const peerId = this.chat.peerId;
 
@@ -541,6 +598,7 @@ export default class ChatInput {
         this.messageInput.removeAttribute('contenteditable');
       } else {
         this.messageInput.setAttribute('contenteditable', 'true');
+        this.setDraft(undefined, false);
       }
       
       this.attachMenu.toggleAttribute('disabled', !visible.length);
@@ -801,7 +859,7 @@ export default class ChatInput {
       alert('not single');
     } */
 
-    //console.log('messageInput input', this.messageInput.innerText, this.serializeNodes(Array.from(this.messageInput.childNodes)));
+    //console.log('messageInput input', this.messageInput.innerText);
     //const value = this.messageInput.innerText;
     const markdownEntities: MessageEntity[] = [];
     const richValue = getRichValue(this.messageInputField.input, markdownEntities);
@@ -896,6 +954,8 @@ export default class ChatInput {
       }
     }
 
+    this.saveDraftDebounced();
+
     this.updateSendBtn();
   };
 
@@ -1039,9 +1099,9 @@ export default class ChatInput {
         }
       });
     } else if(this.helperType == 'reply') {
-      this.chat.setPeer(this.chat.peerId, this.replyToMsgId);
+      this.chat.setMessageId(this.replyToMsgId);
     } else if(this.helperType == 'edit') {
-      this.chat.setPeer(this.chat.peerId, this.editMsgId);
+      this.chat.setMessageId(this.editMsgId);
     }
   };
 
@@ -1125,7 +1185,8 @@ export default class ChatInput {
         noWebPage: this.noWebPage,
         webPage: this.willSendWebPage,
         scheduleDate: this.scheduleDate,
-        silent: this.sendSilent
+        silent: this.sendSilent,
+        clearDraft: true
       });
     }
 
@@ -1269,6 +1330,16 @@ export default class ChatInput {
     this.chat.container.classList.remove('is-helper-active');
   }
 
+  public setInputValue(value: string, clear = true, focus = true) {
+    clear && this.clearInput();
+    this.messageInput.innerHTML = value || '';
+    this.onMessageInput();
+    window.requestAnimationFrame(() => {
+      focus && placeCaretAtEnd(this.messageInput);
+      this.inputScroll.scrollTop = this.inputScroll.scrollHeight;
+    });
+  }
+
   public setTopInfo(type: ChatInputHelperType, callerFunc: () => void, title = '', subtitle = '', input?: string, message?: any) {
     if(type != 'webpage') {
       this.clearHelper(type);
@@ -1288,13 +1359,7 @@ export default class ChatInput {
     } */
 
     if(input !== undefined) {
-      this.clearInput();
-      this.messageInput.innerHTML = input || '';
-      this.onMessageInput();
-      window.requestAnimationFrame(() => {
-        placeCaretAtEnd(this.messageInput);
-        this.inputScroll.scrollTop = this.inputScroll.scrollHeight;
-      });
+      this.setInputValue(input);
     }
 
     setTimeout(() => {
diff --git a/src/components/chat/pinnedMessage.ts b/src/components/chat/pinnedMessage.ts
index 6f350e07..2ed42f51 100644
--- a/src/components/chat/pinnedMessage.ts
+++ b/src/components/chat/pinnedMessage.ts
@@ -542,7 +542,7 @@ export default class ChatPinnedMessage {
   public async followPinnedMessage(mid: number) {
     const message = this.chat.getMessage(mid);
     if(message && !message.deleted) {
-      this.chat.setPeer(this.topbar.peerId, mid);
+      this.chat.setMessageId(mid);
       (this.chat.setPeerPromise || Promise.resolve()).then(() => { // * debounce fast clicker
         this.handleFollowingPinnedMessage();
         this.testMid(this.pinnedIndex >= (this.count - 1) ? this.pinnedMaxMid : mid - 1);
diff --git a/src/components/chat/topbar.ts b/src/components/chat/topbar.ts
index edca464c..1535ef9e 100644
--- a/src/components/chat/topbar.ts
+++ b/src/components/chat/topbar.ts
@@ -208,7 +208,7 @@ export default class ChatTopbar {
   public constructPeerHelpers() {
     this.avatarElement = new AvatarElement();
     this.avatarElement.setAttribute('dialog', '1');
-    this.avatarElement.setAttribute('clickable', '');
+    //this.avatarElement.setAttribute('clickable', '');
     this.avatarElement.classList.add('avatar-40', 'person-avatar');
 
     this.subtitle = document.createElement('div');
diff --git a/src/components/popups/newMedia.ts b/src/components/popups/newMedia.ts
index 93ebe63f..f671604f 100644
--- a/src/components/popups/newMedia.ts
+++ b/src/components/popups/newMedia.ts
@@ -168,7 +168,8 @@ export default class PopupNewMedia extends PopupElement {
           threadId: this.chat.threadId,
           isMedia: willAttach.isMedia,
           silent,
-          scheduleDate
+          scheduleDate,
+          clearDraft: true as true
         }, w));
 
         caption = undefined;
@@ -181,7 +182,8 @@ export default class PopupNewMedia extends PopupElement {
             replyToMsgId: input.replyToMsgId, 
             threadId: this.chat.threadId,
             silent, 
-            scheduleDate
+            scheduleDate,
+            clearDraft: true
           });
           caption = '';
           //input.replyToMsgId = undefined;
@@ -196,7 +198,8 @@ export default class PopupNewMedia extends PopupElement {
           replyToMsgId: input.replyToMsgId,
           threadId: this.chat.threadId,
           silent,
-          scheduleDate
+          scheduleDate,
+          clearDraft: true as true
         }, params));
 
         caption = '';
diff --git a/src/components/scrollable.ts b/src/components/scrollable.ts
index 96ef6865..fa9c118f 100644
--- a/src/components/scrollable.ts
+++ b/src/components/scrollable.ts
@@ -1,7 +1,6 @@
-import { CancellablePromise } from "../helpers/cancellablePromise";
 import { isTouchSupported } from "../helpers/touchSupport";
 import { logger, LogLevels } from "../lib/logger";
-import fastSmoothScroll from "../helpers/fastSmoothScroll";
+import fastSmoothScroll, { FocusDirection } from "../helpers/fastSmoothScroll";
 import useHeavyAnimationCheck from "../hooks/useHeavyAnimationCheck";
 /*
 var el = $0;
@@ -52,7 +51,8 @@ export class ScrollableBase {
   public onScrollMeasure: number = 0;
   protected onScroll: () => void;
 
-  public isHeavyAnimationInProgress: boolean;
+  public isHeavyAnimationInProgress = false;
+  public isHeavyScrolling = false;
 
   constructor(public el: HTMLElement, logPrefix = '', public container: HTMLElement = document.createElement('div')) {
     this.container.classList.add('scrollable');
@@ -87,7 +87,21 @@ export class ScrollableBase {
     this.container.append(element);
   }
 
-  public scrollIntoViewNew = fastSmoothScroll.bind(this, this.container);
+  public scrollIntoViewNew(
+    element: HTMLElement,
+    position: ScrollLogicalPosition,
+    margin?: number,
+    maxDistance?: number,
+    forceDirection?: FocusDirection,
+    forceDuration?: number,
+    axis?: 'x' | 'y'
+  ) {
+    this.isHeavyScrolling = true;
+    return fastSmoothScroll(this.container, element, position, margin, maxDistance, forceDirection, forceDuration, axis)
+    .finally(() => {
+      this.isHeavyScrolling = false;
+    });
+  }
 }
 
 export type SliceSides = 'top' | 'bottom';
diff --git a/src/helpers/fastSmoothScroll.ts b/src/helpers/fastSmoothScroll.ts
index 1a3474ea..48880948 100644
--- a/src/helpers/fastSmoothScroll.ts
+++ b/src/helpers/fastSmoothScroll.ts
@@ -104,6 +104,8 @@ function scrollWithJs(
   const elementRect = element.getBoundingClientRect();
   const containerRect = container.getBoundingClientRect();
 
+  //const transformable = container.firstElementChild as HTMLElement;
+
   const elementPosition = elementRect[rectStartKey] - containerRect[rectStartKey];
   const elementSize = element[scrollSizeKey]; // margin is exclusive in DOMRect
 
@@ -153,6 +155,13 @@ function scrollWithJs(
 
   // console.log('scrollWithJs: will scroll path:', path, element);
 
+  /* let existsTransform = 0;
+  const currentTransform = transformable.style.transform;
+  if(currentTransform) {
+    existsTransform = parseInt(currentTransform.match(/\((.+?), (.+?), .+\)/)[2]);
+    //path += existsTransform;
+  } */
+
   if(path < 0) {
     const remainingPath = -scrollPosition;
     path = Math.max(path, remainingPath);
@@ -167,6 +176,46 @@ function scrollWithJs(
   );
   const startAt = Date.now();
 
+  /* transformable.classList.add('no-transition');
+
+  const tickTransform = () => {
+    const t = duration ? Math.min((Date.now() - startAt) / duration, 1) : 1;
+    const currentPath = path * transition(t);
+
+    transformable.style.transform = `translate3d(0, ${-currentPath}px, 0)`;
+    container.dataset.translate = '' + -currentPath;
+
+    const willContinue = t < 1;
+    if(!willContinue) {
+      fastRaf(() => {
+        delete container.dataset.transform;
+        container.dataset.transform = '';
+        transformable.style.transform = '';
+        void transformable.offsetLeft; // reflow
+        transformable.classList.remove('no-transition');
+        void transformable.offsetLeft; // reflow
+        container[scrollPositionKey] = Math.round(target);
+      });
+    }
+
+    return willContinue;
+  };
+  
+  return animateSingle(tickTransform, container); */
+
+  /* return new Promise((resolve) => {
+    fastRaf(() => {
+      transformable.style.transform = '';
+      transformable.style.transition = '';
+
+      setTimeout(resolve, duration);
+    });
+  });
+
+  const transformableHeight = transformable.scrollHeight;
+  //transformable.style.minHeight = `${transformableHeight}px`;
+  */
+
   const tick = () => {
     const t = duration ? Math.min((Date.now() - startAt) / duration, 1) : 1;
 
@@ -182,6 +231,24 @@ function scrollWithJs(
     return Promise.resolve();
   }
 
+  /* return new Promise((resolve) => {
+    setTimeout(resolve, duration);
+  }).then(() => {
+    transformable.classList.add('no-transition');
+    void transformable.offsetLeft; // reflow
+    transformable.style.transform = '';
+    transformable.style.transition = '';
+    void transformable.offsetLeft; // reflow
+    transformable.classList.remove('no-transition');
+    void transformable.offsetLeft; // reflow
+    fastRaf(() => {
+      
+      container[scrollPositionKey] = Math.round(target);
+      //transformable.style.minHeight = ``;
+    });
+    
+  }); */
+
   return animateSingle(tick, container);
 }
 
diff --git a/src/layer.d.ts b/src/layer.d.ts
index c0a9c0cf..c25e4722 100644
--- a/src/layer.d.ts
+++ b/src/layer.d.ts
@@ -831,7 +831,8 @@ export namespace Message {
 		deleted?: boolean,
 		peerId?: number,
 		fromId?: number,
-		canBeEdited?: boolean
+		canBeEdited?: boolean,
+		rReply?: string
 	};
 
 	export type messageService = {
@@ -856,7 +857,8 @@ export namespace Message {
 		deleted?: boolean,
 		peerId?: number,
 		fromId?: number,
-		canBeEdited?: boolean
+		canBeEdited?: boolean,
+		rReply?: string
 	};
 }
 
@@ -4777,7 +4779,9 @@ export namespace DraftMessage {
 		reply_to_msg_id?: number,
 		message: string,
 		entities?: Array<MessageEntity>,
-		date: number
+		date: number,
+		rReply?: string,
+		rMessage?: string
 	};
 }
 
diff --git a/src/lib/appManagers/apiUpdatesManager.ts b/src/lib/appManagers/apiUpdatesManager.ts
index 12667128..aa6c3eae 100644
--- a/src/lib/appManagers/apiUpdatesManager.ts
+++ b/src/lib/appManagers/apiUpdatesManager.ts
@@ -137,12 +137,15 @@ export class ApiUpdatesManager {
     }
   }
 
-  processUpdateMessage = (updateMessage: any) => {
+  processUpdateMessage = (updateMessage: any, options: Partial<{
+    ignoreSyncLoading: boolean
+  }> = {}) => {
     // return forceGetDifference()
-    var processOpts = {
+    const processOpts = {
       date: updateMessage.date,
       seq: updateMessage.seq,
-      seqStart: updateMessage.seq_start
+      seqStart: updateMessage.seq_start,
+      ignoreSyncLoading: options.ignoreSyncLoading
     };
   
     switch(updateMessage._) {
@@ -389,7 +392,12 @@ export class ApiUpdatesManager {
     return this.channelStates[channelId];
   }
 
-  public processUpdate(update: any, options: any = {}) {
+  public processUpdate(update: any, options: Partial<{
+    date: number,
+    seq: number,
+    seqStart: number,
+    ignoreSyncLoading: boolean
+  }> = {}) {
     let channelId = 0;
     switch(update._) {
       case 'updateNewChannelMessage':
@@ -411,7 +419,7 @@ export class ApiUpdatesManager {
   
     // this.log.log('process', channelId, curState.pts, update)
   
-    if(curState.syncLoading) {
+    if(curState.syncLoading && !options.ignoreSyncLoading) {
       return false;
     }
   
@@ -557,10 +565,10 @@ export class ApiUpdatesManager {
           this.updatesState.seq = stateResult.seq;
           this.updatesState.pts = stateResult.pts;
           this.updatesState.date = stateResult.date;
-          setTimeout(() => {
+          //setTimeout(() => {
             this.updatesState.syncLoading = false;
             //rootScope.broadcast('state_synchronized');
-          }, 1000);
+          //}, 1000);
       
         // ! for testing
         // updatesState.seq = 1
diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts
index d1dd85e4..7c79ac33 100644
--- a/src/lib/appManagers/appDialogsManager.ts
+++ b/src/lib/appManagers/appDialogsManager.ts
@@ -24,6 +24,7 @@ import Button from "../../components/button";
 import SetTransition from "../../components/singleTransition";
 import AppStorage from '../storage';
 import apiUpdatesManager from "./apiUpdatesManager";
+import appDraftsManager, { MyDraftMessage } from "./appDraftsManager";
 
 type DialogDom = {
   avatarEl: AvatarElement,
@@ -335,6 +336,13 @@ export class AppDialogsManager {
       }
     });
 
+    rootScope.on('dialog_draft', (e) => {
+      const dialog = appMessagesManager.getDialogByPeerId(e.peerId)[0];
+      if(dialog) {
+        this.updateDialog(dialog);
+      }
+    });
+
     rootScope.on('peer_changed', (e) => {
       const peerId = e;
 
@@ -458,6 +466,11 @@ export class AppDialogsManager {
         }
       });
 
+      if(state.dialogs?.length) {
+        appDraftsManager.getAllDrafts();
+        appDraftsManager.addMissedDialogs();
+      }
+
       return this.loadDialogs();
     }).then(() => {
       appMessagesManager.getConversationsAll('', 0).finally(() => {
@@ -903,7 +916,7 @@ export class AppDialogsManager {
     });
   }
 
-  public setLastMessage(dialog: any, lastMessage?: any, dom?: DialogDom, highlightWord?: string) {
+  public setLastMessage(dialog: Dialog, lastMessage?: any, dom?: DialogDom, highlightWord?: string) {
     ///////console.log('setlastMessage:', lastMessage);
     if(!dom) {
       dom = this.getDialogDom(dialog.peerId);
@@ -914,7 +927,12 @@ export class AppDialogsManager {
       }
     }
 
+    let draftMessage: MyDraftMessage;
     if(!lastMessage) {
+      if(dialog.draft && dialog.draft._ === 'draftMessage') {
+        draftMessage = dialog.draft;
+      }
+      
       lastMessage = appMessagesManager.getMessageByPeer(dialog.peerId, dialog.top_message);
     }
 
@@ -959,6 +977,8 @@ export class AppDialogsManager {
         });
 
         dom.lastMessageSpan.innerHTML = lastMessageText + messageWrapped;
+      } else if(draftMessage) {
+        dom.lastMessageSpan.innerHTML = draftMessage.rReply;
       } else if(!lastMessage.deleted) {
         dom.lastMessageSpan.innerHTML = lastMessage.rReply;
       } else {
@@ -966,7 +986,12 @@ export class AppDialogsManager {
       }
   
       /* if(lastMessage.from_id == auth.id) { // You:  */
-      if(peer._ != 'peerUser' && peerId != lastMessage.fromId && !lastMessage.action) {
+      if(draftMessage) {
+        const bold = document.createElement('b');
+        bold.classList.add('danger');
+        bold.innerHTML = 'Draft: ';
+        dom.lastMessageSpan.prepend(bold);
+      } else if(peer._ !== 'peerUser' && peerId !== lastMessage.fromId && !lastMessage.action) {
         const sender = appPeersManager.getPeer(lastMessage.fromId);
         if(sender && sender.id) {
           const senderBold = document.createElement('b');
@@ -987,8 +1012,9 @@ export class AppDialogsManager {
       }
     }
 
-    if(!lastMessage.deleted) {
-      dom.lastTimeSpan.innerHTML = formatDateAccordingToToday(new Date(lastMessage.date * 1000));
+    if(!lastMessage.deleted || draftMessage/*  && lastMessage._ !== 'draftMessage' */) {
+      const date = draftMessage ? Math.max(draftMessage.date, lastMessage.date || 0) : lastMessage.date;
+      dom.lastTimeSpan.innerHTML = formatDateAccordingToToday(new Date(date * 1000));
     } else dom.lastTimeSpan.innerHTML = '';
 
     if(this.doms[peerId] == dom) {
@@ -1024,7 +1050,9 @@ export class AppDialogsManager {
       dom.listEl.classList.toggle('is-muted', isMuted);
     }
 
-    const lastMessage = appMessagesManager.getMessageByPeer(dialog.peerId, dialog.top_message);
+    const lastMessage = dialog.draft && dialog.draft._ === 'draftMessage' ? 
+      dialog.draft : 
+      appMessagesManager.getMessageByPeer(dialog.peerId, dialog.top_message);
     if(lastMessage._ != 'messageEmpty' && !lastMessage.deleted && 
       lastMessage.fromId == rootScope.myId && lastMessage.peerId != rootScope.myId/*  && 
       dialog.read_outbox_max_id */) { // maybe comment, 06.20.2020
diff --git a/src/lib/appManagers/appDraftsManager.ts b/src/lib/appManagers/appDraftsManager.ts
new file mode 100644
index 00000000..29d5cc5d
--- /dev/null
+++ b/src/lib/appManagers/appDraftsManager.ts
@@ -0,0 +1,187 @@
+import { MOUNT_CLASS_TO } from "../mtproto/mtproto_config";
+import rootScope from "../rootScope";
+import appPeersManager from "./appPeersManager";
+import appMessagesManager from "./appMessagesManager";
+import apiUpdatesManager from "./apiUpdatesManager";
+import RichTextProcessor from "../richtextprocessor";
+import serverTimeManager from "../mtproto/serverTimeManager";
+import { MessageEntity, DraftMessage, MessagesSaveDraft } from "../../layer";
+import apiManager from "../mtproto/mtprotoworker";
+import { tsNow } from "../../helpers/date";
+import { deepEqual } from "../../helpers/object";
+import appStateManager from "./appStateManager";
+
+export type MyDraftMessage = DraftMessage.draftMessage;
+
+export class AppDraftsManager {
+  private drafts: {[peerIdAndThreadId: string]: MyDraftMessage} = {};
+  private getAllDraftPromise: Promise<void> = null;
+
+  constructor() {
+    appStateManager.getState().then(state => {
+      this.drafts = state.drafts;
+    });
+
+    appStateManager.addListener('save', async() => {
+      appStateManager.pushToState('drafts', this.drafts);
+    });
+
+    rootScope.on('apiUpdate', (update) => {
+      if(update._ !== 'updateDraftMessage') {
+        return
+      }
+
+      const peerID = appPeersManager.getPeerId(update.peer);
+      this.saveDraft(peerID, (update as any).threadId, update.draft, {notify: true});
+    });
+  }
+
+  private getKey(peerId: number, threadId?: number) {
+    return '' + peerId + (threadId ? '_' + threadId : '');
+  }
+
+  public getDraft(peerId: number, threadId?: number) {
+    return this.drafts[this.getKey(peerId, threadId)];
+  }
+
+  public addMissedDialogs() {
+    return this.getAllDrafts().then(() => {
+      for(const key in this.drafts) {
+        if(key.indexOf('_') !== -1) { // exclude threads
+          continue;
+        }
+
+        const peerId = +key;
+        const dialog = appMessagesManager.getDialogByPeerId(peerId)[0];
+        if(!dialog) {
+          appMessagesManager.reloadConversation(peerId);
+          /* const dialog = appMessagesManager.generateDialog(peerId);
+          dialog.draft = this.drafts[key];
+          appMessagesManager.saveConversation(dialog);
+          appMessagesManager.newDialogsToHandle[peerId] = dialog;
+          appMessagesManager.scheduleHandleNewDialogs(); */
+        }
+      }
+    });
+  }
+
+  public getAllDrafts() {
+    return this.getAllDraftPromise || (this.getAllDraftPromise = new Promise((resolve) => {
+      apiManager.invokeApi('messages.getAllDrafts').then((updates) => {
+        apiUpdatesManager.processUpdateMessage(updates, {ignoreSyncLoading: true});
+        resolve();
+      });
+    }));
+  }
+
+  public saveDraft(peerId: number, threadId: number, apiDraft: DraftMessage, options: Partial<{
+    notify: boolean
+  }> = {}) {
+    const draft = this.processApiDraft(apiDraft);
+
+    const key = this.getKey(peerId, threadId);
+    if(draft) {
+      this.drafts[key] = draft;
+    } else {
+      delete this.drafts[key];
+    }
+
+    if(options.notify) {
+      // console.warn(dT(), 'save draft', peerId, apiDraft, options)
+      rootScope.broadcast('draft_updated', {
+        peerId,
+        threadId,
+        draft
+      });
+    }
+
+    return draft;
+  }
+
+  public draftsAreEqual(draft1: DraftMessage, draft2: DraftMessage) {
+    return deepEqual(draft1, draft2);
+  }
+
+  public isEmptyDraft(draft: DraftMessage) {
+    if(!draft || draft._ === 'draftMessageEmpty') {
+      return true;
+    }
+    
+    if(draft.reply_to_msg_id > 0) {
+      return false;
+    }
+    
+    if(!draft.message.length) {
+      return true;
+    }
+    
+    return false;
+  }
+
+  public processApiDraft(draft: DraftMessage): MyDraftMessage {
+    if(!draft || draft._ !== 'draftMessage') {
+      return undefined;
+    }
+
+    const myEntities = RichTextProcessor.parseEntities(draft.message);
+    const apiEntities = draft.entities || [];
+    const totalEntities = RichTextProcessor.mergeEntities(apiEntities, myEntities); // ! only in this order, otherwise bold and emoji formatting won't work
+
+    draft.rMessage = RichTextProcessor.wrapDraftText(draft.message, {entities: totalEntities});
+    draft.rReply = appMessagesManager.getRichReplyText(draft);
+    if(draft.reply_to_msg_id) {
+      draft.reply_to_msg_id = appMessagesManager.generateMessageId(draft.reply_to_msg_id);
+    }
+
+    return draft;
+  }
+
+  public syncDraft(peerId: number, threadId: number, localDraft?: MyDraftMessage, saveOnServer = true) {
+    // console.warn(dT(), 'sync draft', peerID)
+    const serverDraft = this.getDraft(peerId, threadId);
+    if(this.draftsAreEqual(serverDraft, localDraft)) {
+      // console.warn(dT(), 'equal drafts', localDraft, serverDraft)
+      return;
+    }
+
+    // console.warn(dT(), 'changed draft', localDraft, serverDraft)
+    let params: MessagesSaveDraft = {
+      peer: appPeersManager.getInputPeerById(peerId),
+      message: ''
+    };
+
+    let draftObj: DraftMessage;
+    if(this.isEmptyDraft(localDraft)) {
+      draftObj = {_: 'draftMessageEmpty'};
+    } else {
+      draftObj = {_: 'draftMessage'} as any as DraftMessage.draftMessage;
+      let message = localDraft.message;
+      let entities: MessageEntity[] = [];
+      //message = RichTextProcessor.parseEmojis(message);
+      //message = RichTextProcessor.parseMarkdown(message, entities, true);
+
+      if(localDraft.reply_to_msg_id) {
+        params.reply_to_msg_id = draftObj.reply_to_msg_id = localDraft.reply_to_msg_id;
+      }
+
+      if(entities.length) {
+        params.entities = draftObj.entities = entities;
+      }
+
+      params.message = draftObj.message = message;
+    }
+
+    draftObj.date = tsNow(true) + serverTimeManager.serverTimeOffset;
+    this.saveDraft(peerId, threadId, draftObj, {notify: true});
+
+    if(saveOnServer && !threadId) {
+      apiManager.invokeApi('messages.saveDraft', params).then(() => {
+
+      });
+    }
+  }
+}
+
+const appDraftsManager = new AppDraftsManager();
+MOUNT_CLASS_TO && (MOUNT_CLASS_TO.appDraftsManager = appDraftsManager);
+export default appDraftsManager;
\ No newline at end of file
diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts
index 93dd6f79..a25259b1 100644
--- a/src/lib/appManagers/appImManager.ts
+++ b/src/lib/appManagers/appImManager.ts
@@ -30,6 +30,8 @@ import ChatDragAndDrop from '../../components/chat/dragAndDrop';
 import { debounce } from '../../helpers/schedulers';
 import lottieLoader from '../lottieLoader';
 import useHeavyAnimationCheck from '../../hooks/useHeavyAnimationCheck';
+import appDraftsManager from './appDraftsManager';
+import serverTimeManager from '../mtproto/serverTimeManager';
 
 //console.log('appImManager included33!');
 
@@ -450,7 +452,7 @@ export class AppImManager {
   }
 
   private createNewChat() {
-    const chat = new Chat(this, appChatsManager, appDocsManager, appInlineBotsManager, appMessagesManager, appPeersManager, appPhotosManager, appProfileManager, appStickersManager, appUsersManager, appWebPagesManager, appPollsManager, apiManager);
+    const chat = new Chat(this, appChatsManager, appDocsManager, appInlineBotsManager, appMessagesManager, appPeersManager, appPhotosManager, appProfileManager, appStickersManager, appUsersManager, appWebPagesManager, appPollsManager, apiManager, appDraftsManager, serverTimeManager);
 
     this.chats.push(chat);
   }
@@ -458,6 +460,10 @@ export class AppImManager {
   private spliceChats(fromIndex: number, justReturn = true) {
     if(fromIndex >= this.chats.length) return;
 
+    if(this.chats.length > 1 && justReturn) {
+      rootScope.broadcast('peer_changing', this.chat);
+    }
+
     const spliced = this.chats.splice(fromIndex, this.chats.length - fromIndex);
 
     // * fix middle chat z-index on animation
@@ -561,7 +567,7 @@ export class AppImManager {
 
   public setInnerPeer(peerId: number, lastMsgId?: number, type: ChatType = 'chat', threadId?: number) {
     // * prevent opening already opened peer
-    const existingIndex = this.chats.findIndex(chat => chat.peerId == peerId && chat.type == type);
+    const existingIndex = this.chats.findIndex(chat => chat.peerId === peerId && chat.type === type);
     if(existingIndex !== -1) {
       this.spliceChats(existingIndex + 1);
       return this.setPeer(peerId, lastMsgId);
diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts
index c0824e79..3ec12cbd 100644
--- a/src/lib/appManagers/appMessagesManager.ts
+++ b/src/lib/appManagers/appMessagesManager.ts
@@ -24,7 +24,7 @@ import DialogsStorage from "../storages/dialogs";
 import FiltersStorage from "../storages/filters";
 //import { telegramMeWebService } from "../mtproto/mtproto";
 import apiUpdatesManager from "./apiUpdatesManager";
-import appChatsManager from "./appChatsManager";
+import appChatsManager, { Channel } from "./appChatsManager";
 import appDocsManager, { MyDocument } from "./appDocsManager";
 import appDownloadManager from "./appDownloadManager";
 import appPeersManager from "./appPeersManager";
@@ -33,6 +33,7 @@ import appPollsManager from "./appPollsManager";
 import appStateManager from "./appStateManager";
 import appUsersManager from "./appUsersManager";
 import appWebPagesManager from "./appWebPagesManager";
+import appDraftsManager from "./appDraftsManager";
 
 //console.trace('include');
 // TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет
@@ -201,41 +202,26 @@ export class AppMessagesManager {
       });
     });
 
-    /* rootScope.$on('draft_updated', (e) => {
-      let eventData = e;;
-      var peerId = eventData.peerId;
-      var draft = eventData.draft;
+    rootScope.on('draft_updated', (e) => {
+      const {peerId, threadId, draft} = e;
 
-      var dialog = this.getDialogByPeerID(peerId)[0];
-      if(dialog) {
-        var topDate;
-        if(draft && draft.date) {
-          topDate = draft.date;
-        } else {
-          var channelId = appPeersManager.isChannel(peerId) ? -peerId : 0
-          var topDate = this.getMessage(dialog.top_message).date;
-
-          if(channelId) {
-            var channel = appChatsManager.getChat(channelId);
-            if(!topDate || channel.date && channel.date > topDate) {
-              topDate = channel.date;
-            }
-          }
-        }
-
-        if(!dialog.pFlags.pinned) {
-          dialog.index = this.dialogsStorage.generateDialogIndex(topDate);
-        }
+      if(threadId) return;
 
+      const dialog = this.getDialogByPeerId(peerId)[0];
+      if(dialog && !threadId) {
+        dialog.draft = draft;
+        this.dialogsStorage.generateIndexForDialog(dialog);
         this.dialogsStorage.pushDialog(dialog);
 
-        rootScope.$broadcast('dialog_draft', {
+        rootScope.broadcast('dialog_draft', {
           peerId,
           draft,
           index: dialog.index
         });
+      } else {
+        this.reloadConversation(peerId);
       }
-    }); */
+    });
 
     function timedChunk(items: any[], process: (...args: any[]) => any, context: any, callback: (...args: any[]) => void) {
       if(!items.length) return callback(items);
@@ -278,7 +264,7 @@ export class AppMessagesManager {
             }
     
             dialog.top_message = message.mid;
-            this.setDialogIndexByMessage(dialog, message);
+            this.dialogsStorage.generateIndexForDialog(dialog, false, message);
     
             break;
           } else if(message.pFlags && message.pFlags.unread) {
@@ -602,7 +588,11 @@ export class AppMessagesManager {
       this.pendingAfterMsgs[peerId] = sentRequestOptions;
     }
 
-    this.beforeMessageSending(message, {isScheduled: !!options.scheduleDate || undefined, threadId: options.threadId});
+    this.beforeMessageSending(message, {
+      isScheduled: !!options.scheduleDate || undefined, 
+      threadId: options.threadId,
+      clearDraft: options.clearDraft
+    });
   }
 
   public sendFile(peerId: number, file: File | Blob | MyDocument, options: Partial<{
@@ -623,6 +613,7 @@ export class AppMessagesManager {
     duration: number,
     background: true,
     silent: true,
+    clearDraft: true,
     scheduleDate: number,
 
     waveform: Uint8Array
@@ -957,7 +948,12 @@ export class AppMessagesManager {
       return sentDeferred;
     };
 
-    this.beforeMessageSending(message, {isGroupedItem: options.isGroupedItem, isScheduled: !!options.scheduleDate || undefined, threadId: options.threadId});
+    this.beforeMessageSending(message, {
+      isGroupedItem: options.isGroupedItem, 
+      isScheduled: !!options.scheduleDate || undefined, 
+      threadId: options.threadId,
+      clearDraft: options.clearDraft
+    });
 
     if(!options.isGroupedItem) {
       sentDeferred.then(inputMedia => {
@@ -972,7 +968,8 @@ export class AppMessagesManager {
           reply_to_msg_id: replyToMsgId,
           schedule_date: options.scheduleDate,
           silent: options.silent,
-          entities
+          entities,
+          clear_draft: options.clearDraft
         }).then((updates) => {
           apiUpdatesManager.processUpdateMessage(updates);
         }, (error) => {
@@ -1009,6 +1006,7 @@ export class AppMessagesManager {
       thumbURL: string
     }>[],
     silent: true,
+    clearDraft: true,
     scheduleDate: number
   }> = {}) {
     //this.checkSendOptions(options);
@@ -1065,6 +1063,10 @@ export class AppMessagesManager {
 
       this.setDialogTopMessage(message);
     }
+
+    if(options.clearDraft) {
+      appDraftsManager.syncDraft(peerId, options.threadId);
+    }
     
     // * test pending
     //return;
@@ -1090,7 +1092,8 @@ export class AppMessagesManager {
             multi_media: multiMedia,
             reply_to_msg_id: replyToMsgId,
             schedule_date: options.scheduleDate,
-            silent: options.silent
+            silent: options.silent,
+            clear_draft: options.clearDraft
           }).then((updates) => {
             apiUpdatesManager.processUpdateMessage(updates);
           }, (error) => {
@@ -1332,7 +1335,11 @@ export class AppMessagesManager {
       this.pendingAfterMsgs[peerId] = sentRequestOptions;
     }
 
-    this.beforeMessageSending(message, {isScheduled: !!options.scheduleDate || undefined, threadId: options.threadId});
+    this.beforeMessageSending(message, {
+      isScheduled: !!options.scheduleDate || undefined, 
+      threadId: options.threadId,
+      clearDraft: options.clearDraft
+    });
   }
 
   /* private checkSendOptions(options: Partial<{
@@ -1346,7 +1353,12 @@ export class AppMessagesManager {
     }
   } */
 
-  private beforeMessageSending(message: any, options: Partial<{isGroupedItem: true, isScheduled: true, threadId: number}> = {}) {
+  private beforeMessageSending(message: any, options: Partial<{
+    isGroupedItem: true, 
+    isScheduled: true, 
+    threadId: number, 
+    clearDraft: true
+  }> = {}) {
     const messageId = message.id;
     const peerId = this.getMessagePeer(message);
     const storage = options.isScheduled ? this.getScheduledMessagesStorage(peerId) : this.getMessagesStorage(peerId);
@@ -1375,6 +1387,10 @@ export class AppMessagesManager {
         this.setDialogTopMessage(message);
       }
     }
+
+    if(!options.isGroupedItem && options.clearDraft && !options.threadId) {
+      appDraftsManager.syncDraft(peerId, options.threadId);
+    }
     
     this.pendingByRandomId[message.random_id] = {peerId, tempId: messageId, storage};
 
@@ -1427,12 +1443,6 @@ export class AppMessagesManager {
     return pFlags;
   }
 
-  private setDialogIndexByMessage(dialog: MTDialog.dialog, message: MyMessage) {
-    if(!dialog.pFlags.pinned || !dialog.index) {
-      dialog.index = this.dialogsStorage.generateDialogIndex(message.date);
-    }
-  }
-
   public setDialogTopMessage(message: MyMessage, dialog: MTDialog.dialog = this.getDialogByPeerId(message.peerId)[0]) {
     if(dialog) {
       dialog.top_message = message.mid;
@@ -1440,7 +1450,7 @@ export class AppMessagesManager {
       const historyStorage = this.getHistoryStorage(message.peerId);
       historyStorage.maxId = message.mid;
 
-      this.setDialogIndexByMessage(dialog, message);
+      this.dialogsStorage.generateIndexForDialog(dialog, false, message);
 
       this.newDialogsToHandle[message.peerId] = dialog;
       this.scheduleHandleNewDialogs();
@@ -1587,28 +1597,16 @@ export class AppMessagesManager {
     let offsetDate = 0;
     let offsetPeerId = 0;
     let offsetIndex = 0;
-    let flags = 0;
 
     if(this.dialogsStorage.dialogsOffsetDate[folderId]) {
       offsetDate = this.dialogsStorage.dialogsOffsetDate[folderId] + serverTimeManager.serverTimeOffset;
       offsetIndex = this.dialogsStorage.dialogsOffsetDate[folderId] * 0x10000;
-      //flags |= 1; // means pinned already loaded
     }
 
-    /* if(this.dialogsStorage.dialogsOffsetDate[0]) {
-      flags |= 1; // means pinned already loaded
-    } */
-
-    //if(folderId > 0) {
-      //flags |= 1;
-      flags |= 2;
-    //}
-
     // ! ВНИМАНИЕ: ОЧЕНЬ СЛОЖНАЯ ЛОГИКА:
     // ! если делать запрос сначала по папке 0, потом по папке 1, по индексу 0 в массиве будет один и тот же диалог, с dialog.pFlags.pinned, ЛОЛ???
     // ! т.е., с запросом folder_id: 1, и exclude_pinned: 0, в результате будут ещё и закреплённые с папки 0
     return apiManager.invokeApi('messages.getDialogs', {
-      flags,
       folder_id: folderId,
       offset_date: offsetDate,
       offset_id: offsetId,
@@ -1621,7 +1619,9 @@ export class AppMessagesManager {
     }).then((dialogsResult) => {
       if(dialogsResult._ == 'messages.dialogsNotModified') return null;
 
-      //this.log.error('messages.getDialogs result:', dialogsResult.dialogs, {...dialogsResult.dialogs[0]});
+      if(DEBUG) {
+        this.log('messages.getDialogs result:', dialogsResult.dialogs, {...dialogsResult.dialogs[0]});
+      }
 
       /* if(!offsetDate) {
         telegramMeWebService.setAuthorized(true);
@@ -2659,7 +2659,7 @@ export class AppMessagesManager {
     appChatsManager.saveApiChats(dialogsResult.chats);
     this.saveMessages(dialogsResult.messages);
 
-    //this.log('applyConversation', dialogsResult);
+    this.log('applyConversation', dialogsResult);
 
     const updatedDialogs: {[peerId: number]: Dialog} = {};
     (dialogsResult.dialogs as Dialog[]).forEach((dialog) => {
@@ -2679,7 +2679,7 @@ export class AppMessagesManager {
         this.log.error('applyConversation lun', dialog, d);
       } */
 
-      if(topMessage) {
+      if(topMessage || (dialog.draft && dialog.draft._ === 'draftMessage')) {
         //const wasDialogBefore = this.getDialogByPeerID(peerId)[0];
 
         // here need to just replace, not FULL replace dialog! WARNING
@@ -2720,6 +2720,24 @@ export class AppMessagesManager {
     }
   }
 
+  public generateDialog(peerId: number) {
+    const dialog: Dialog = {
+      _: 'dialog',
+      pFlags: {},
+      peer: appPeersManager.getOutputPeer(peerId),
+      top_message: 0,
+      read_inbox_max_id: 0,
+      read_outbox_max_id: 0,
+      unread_count: 0,
+      unread_mentions_count: 0,
+      notify_settings: {
+        _: 'peerNotifySettings',
+      },
+    };
+
+    return dialog;
+  }
+
   public saveConversation(dialog: Dialog, folderId = 0) {
     const peerId = appPeersManager.getPeerId(dialog.peer);
     if(!peerId) {
@@ -2743,7 +2761,7 @@ export class AppMessagesManager {
       message = {
         _: 'message',
         id: mid,
-        mid: mid,
+        mid,
         from_id: appPeersManager.getOutputPeer(appUsersManager.getSelf().id),
         peer_id: appPeersManager.getOutputPeer(peerId),
         deleted: true,
@@ -2783,6 +2801,7 @@ export class AppMessagesManager {
       } */
     }
 
+    dialog.draft = appDraftsManager.saveDraft(peerId, 0, dialog.draft);
     dialog.peerId = peerId;
 
     // Because we saved message without dialog present
@@ -4717,9 +4736,9 @@ export class AppMessagesManager {
   }
 
   public setTyping(peerId: number, _action: any): Promise<boolean> {
-    if(!rootScope.myId || !peerId || !this.canWriteToPeer(peerId)) return Promise.resolve(false);
+    if(!rootScope.myId || !peerId || !this.canWriteToPeer(peerId) || peerId === rootScope.myId) return Promise.resolve(false);
     
-    const action: SendMessageAction = typeof(_action) == 'string' ? {_: _action} : _action;
+    const action: SendMessageAction = typeof(_action) === 'string' ? {_: _action} : _action;
     return apiManager.invokeApi('messages.setTyping', {
       peer: appPeersManager.getInputPeerById(peerId),
       action
diff --git a/src/lib/appManagers/appStateManager.ts b/src/lib/appManagers/appStateManager.ts
index e1cad97f..06940f9d 100644
--- a/src/lib/appManagers/appStateManager.ts
+++ b/src/lib/appManagers/appStateManager.ts
@@ -11,6 +11,7 @@ import type { AuthState } from '../../types';
 import type FiltersStorage from '../storages/filters';
 import type DialogsStorage from '../storages/dialogs';
 import { copy, setDeepProperty, isObject, validateInitObject } from '../../helpers/object';
+import { AppDraftsManager } from './appDraftsManager';
 
 const REFRESH_EVERY = 24 * 60 * 60 * 1000; // 1 day
 const STATE_VERSION = App.version;
@@ -55,7 +56,8 @@ export type State = Partial<{
       suggest: boolean,
       loop: boolean
     }
-  }
+  },
+  drafts: AppDraftsManager['drafts']
 }>;
 
 const STATE_INIT: State = {
@@ -96,7 +98,8 @@ const STATE_INIT: State = {
       suggest: true,
       loop: true
     }
-  }
+  },
+  drafts: {}
 };
 
 const ALL_KEYS = Object.keys(STATE_INIT) as any as Array<keyof State>;
diff --git a/src/lib/langPack.ts b/src/lib/langPack.ts
index 87ba739e..f46cd680 100644
--- a/src/lib/langPack.ts
+++ b/src/lib/langPack.ts
@@ -9,7 +9,7 @@ export const langPack: {[actionType: string]: string} = {
   "messageActionChatAddUsers": "invited {} users",
 	"messageActionChatLeave": "left the group",
 	"messageActionChatDeleteUser": "removed user {}",
-	"messageActionChatJoinedByLink": "joined the group",
+	"messageActionChatJoinedByLink": "joined the group via invite link",
   "messageActionPinMessage": "pinned message",
   "messageActionContactSignUp": "joined Telegram",
 	"messageActionChannelCreate": "Channel created",
diff --git a/src/lib/mtproto/mtproto.worker.ts b/src/lib/mtproto/mtproto.worker.ts
index 15ed79a2..da42b587 100644
--- a/src/lib/mtproto/mtproto.worker.ts
+++ b/src/lib/mtproto/mtproto.worker.ts
@@ -56,8 +56,8 @@ export const isWebpSupported = () => {
   return webpSupported;
 };
 
-networkerFactory.setUpdatesProcessor((obj, bool) => {
-  respond({update: {obj, bool}});
+networkerFactory.setUpdatesProcessor((obj) => {
+  respond({update: obj});
 });
 
 networkerFactory.onConnectionStatusChange = (status) => {
diff --git a/src/lib/mtproto/mtprotoworker.ts b/src/lib/mtproto/mtprotoworker.ts
index 56cecfe9..4758ed3b 100644
--- a/src/lib/mtproto/mtprotoworker.ts
+++ b/src/lib/mtproto/mtprotoworker.ts
@@ -46,7 +46,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
   } = {} as any;
   private pending: Array<Task> = [];
 
-  public updatesProcessor: (obj: any, bool: boolean) => void = null;
+  public updatesProcessor: (obj: any) => void = null;
 
   private log = logger('API-PROXY');
 
@@ -134,7 +134,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
       
       if(task.update) {
         if(this.updatesProcessor) {
-          this.updatesProcessor(task.update.obj, task.update.bool);
+          this.updatesProcessor(task.update);
         }
       } else if(task.progress) {
         rootScope.broadcast('download_progress', task.progress);
@@ -218,7 +218,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
     }
   }
 
-  public setUpdatesProcessor(callback: (obj: any, bool: boolean) => void) {
+  public setUpdatesProcessor(callback: (obj: any) => void) {
     this.updatesProcessor = callback;
   }
 
diff --git a/src/lib/mtproto/networker.ts b/src/lib/mtproto/networker.ts
index 2dd050c7..3dd9de19 100644
--- a/src/lib/mtproto/networker.ts
+++ b/src/lib/mtproto/networker.ts
@@ -1262,7 +1262,7 @@ export default class MTPNetworker {
   
         AppStorage.get<number>('dc').then((baseDcId: number) => {
           if(baseDcId == this.dcId && !this.isFileNetworker && NetworkerFactory.updatesProcessor) {
-            NetworkerFactory.updatesProcessor(message, true);
+            NetworkerFactory.updatesProcessor(message);
           }
         });
         break;
@@ -1360,7 +1360,7 @@ export default class MTPNetworker {
         this.log.debug('Update', message);
         
         if(NetworkerFactory.updatesProcessor !== null) {
-          NetworkerFactory.updatesProcessor(message, true);
+          NetworkerFactory.updatesProcessor(message);
         }
         break;
     }
diff --git a/src/lib/mtproto/networkerFactory.ts b/src/lib/mtproto/networkerFactory.ts
index 79551149..a76a4c1c 100644
--- a/src/lib/mtproto/networkerFactory.ts
+++ b/src/lib/mtproto/networkerFactory.ts
@@ -3,10 +3,10 @@ import { ConnectionStatusChange, InvokeApiOptions } from "../../types";
 import MTTransport from "./transports/transport";
 
 export class NetworkerFactory {
-  public updatesProcessor: (obj: any, bool: boolean) => void = null;
+  public updatesProcessor: (obj: any) => void = null;
   public onConnectionStatusChange: (info: ConnectionStatusChange) => void = null;
 
-  public setUpdatesProcessor(callback: (obj: any, bool: boolean) => void) {
+  public setUpdatesProcessor(callback: (obj: any) => void) {
     this.updatesProcessor = callback;
   }
 
diff --git a/src/lib/rootScope.ts b/src/lib/rootScope.ts
index 8b3d9491..1af79907 100644
--- a/src/lib/rootScope.ts
+++ b/src/lib/rootScope.ts
@@ -5,14 +5,17 @@ import type { Poll, PollResults } from "./appManagers/appPollsManager";
 import type { MyDialogFilter } from "./storages/filters";
 import type { ConnectionStatusChange } from "../types";
 import type { UserTyping } from "./appManagers/appChatsManager";
+import type Chat from "../components/chat/chat";
 import { DEBUG, MOUNT_CLASS_TO, UserAuth } from "./mtproto/mtproto_config";
 import { State } from "./appManagers/appStateManager";
 import EventListenerBase from "../helpers/eventListenerBase";
+import { MyDraftMessage } from "./appManagers/appDraftsManager";
 
 type BroadcastEvents = {
   'user_update': number,
   'user_auth': UserAuth,
   'peer_changed': number,
+  'peer_changing': Chat,
   'peer_pinned_messages': {peerId: number, mids?: number[], pinned?: boolean, unpinAll?: true},
   'peer_pinned_hidden': {peerId: number, maxId: number},
   'peer_typings': {peerId: number, typings: UserTyping[]},
@@ -21,7 +24,7 @@ type BroadcastEvents = {
   'filter_update': MyDialogFilter,
   'filter_order': number[],
   
-  'dialog_draft': {peerId: number, draft: any, index: number},
+  'dialog_draft': {peerId: number, draft: MyDraftMessage | undefined, index: number},
   'dialog_unread': {peerId: number},
   'dialog_flush': {peerId: number},
   'dialog_drop': {peerId: number, dialog?: Dialog},
@@ -76,7 +79,7 @@ type BroadcastEvents = {
   'download_progress': any,
   'connection_status_change': ConnectionStatusChange,
   'settings_updated': {key: string, value: any},
-  //'draft_updated': any,
+  'draft_updated': {peerId: number, threadId: number, draft: MyDraftMessage | undefined},
 
   'event-heavy-animation-start': void,
   'event-heavy-animation-end': void
diff --git a/src/lib/storages/dialogs.ts b/src/lib/storages/dialogs.ts
index 93822f00..7259b0f7 100644
--- a/src/lib/storages/dialogs.ts
+++ b/src/lib/storages/dialogs.ts
@@ -1,7 +1,7 @@
 import { tsNow } from "../../helpers/date";
 import type { Message } from "../../layer";
 import type { AppChatsManager } from "../appManagers/appChatsManager";
-import type { AppMessagesManager, Dialog } from "../appManagers/appMessagesManager";
+import type { AppMessagesManager, Dialog, MyMessage } from "../appManagers/appMessagesManager";
 import type { AppPeersManager } from "../appManagers/appPeersManager";
 import type { ServerTimeManager } from "../mtproto/serverTimeManager";
 
@@ -89,27 +89,32 @@ export default class DialogsStorage {
     return (date * 0x10000) + ((++this.dialogsNum) & 0xFFFF);
   }
 
-  public generateIndexForDialog(dialog: Dialog, justReturn = false) {
+  public generateIndexForDialog(dialog: Dialog, justReturn = false, message?: MyMessage) {
     const channelId = this.appPeersManager.isChannel(dialog.peerId) ? -dialog.peerId : 0;
-    const mid = dialog.top_message;
-    const message = this.appMessagesManager.getMessageByPeer(dialog.peerId, mid);
+    
+    let topDate = 0;
+    if(dialog.pFlags.pinned && !justReturn) {
+      topDate = this.generateDialogPinnedDate(dialog);
+    } else {
+      if(!message) {
+        message = this.appMessagesManager.getMessageByPeer(dialog.peerId, dialog.top_message);
+      }
 
-    let topDate = (message as Message.message).date || Date.now() / 1000;
-    if(channelId) {
-      const channel = this.appChatsManager.getChat(channelId);
-      if(!topDate || channel.date && channel.date > topDate) {
-        topDate = channel.date;
+      topDate = (message as Message.message).date || topDate;
+      if(channelId) {
+        const channel = this.appChatsManager.getChat(channelId);
+        if(!topDate || (channel.date && channel.date > topDate)) {
+          topDate = channel.date;
+        }
+      }
+  
+      if(dialog.draft && dialog.draft._ === 'draftMessage' && dialog.draft.date > topDate) {
+        topDate = dialog.draft.date;
       }
     }
 
-    const savedDraft: any = {};// DraftsManager.saveDraft(peerId, dialog.draft); // warning
-    if(savedDraft && savedDraft.date > topDate) {
-      topDate = savedDraft.date;
-    }
-
-    if(dialog.pFlags.pinned && !justReturn) {
-      topDate = this.generateDialogPinnedDate(dialog);
-      //this.log('topDate', peerId, topDate);
+    if(!topDate) {
+      topDate = Date.now() / 1000;
     }
 
     const index = this.generateDialogIndex(topDate);
diff --git a/src/scripts/generate_mtproto_types.js b/src/scripts/generate_mtproto_types.js
index 6196043e..012a8f8b 100644
--- a/src/scripts/generate_mtproto_types.js
+++ b/src/scripts/generate_mtproto_types.js
@@ -255,4 +255,6 @@ for(const method in methodsMap) {
 out += `}\n\n`;
 
 const path = process.argv[2];
-require('fs').writeFileSync((path || __dirname + '/out/') + 'layer.d.ts', out);
\ No newline at end of file
+const writePathTo = (path || __dirname + '/out/') + 'layer.d.ts';
+console.log('Writing layer to:', writePathTo);
+require('fs').writeFileSync(writePathTo, out);
\ No newline at end of file
diff --git a/src/scripts/in/schema_additional_params.json b/src/scripts/in/schema_additional_params.json
index 690a026b..d7c7979d 100644
--- a/src/scripts/in/schema_additional_params.json
+++ b/src/scripts/in/schema_additional_params.json
@@ -70,7 +70,8 @@
     {"name": "fromId", "type": "number"},
     {"name": "grouped_id", "type": "string"},
     {"name": "canBeEdited", "type": "boolean"},
-    {"name": "unread", "type": "true"}
+    {"name": "unread", "type": "true"},
+    {"name": "rReply", "type": "string"}
   ]
 }, {
   "predicate": "messageService",
@@ -80,7 +81,8 @@
     {"name": "peerId", "type": "number"},
     {"name": "fromId", "type": "number"},
     {"name": "canBeEdited", "type": "boolean"},
-    {"name": "unread", "type": "true"}
+    {"name": "unread", "type": "true"},
+    {"name": "rReply", "type": "string"}
   ]
 }, {
   "predicate": "messageEmpty",
@@ -228,4 +230,10 @@
   "params": [
     {"name": "phone_number", "type": "string"}
   ]
+}, {
+  "predicate": "draftMessage",
+  "params": [
+    {"name": "rReply", "type": "string"},
+    {"name": "rMessage", "type": "string"}
+  ]
 }]
\ No newline at end of file