Browse Source

Draft messages

master
morethanwords 3 years ago
parent
commit
38780910dd
  1. 61
      src/components/chat/bubbles.ts
  2. 44
      src/components/chat/chat.ts
  3. 97
      src/components/chat/input.ts
  4. 2
      src/components/chat/pinnedMessage.ts
  5. 2
      src/components/chat/topbar.ts
  6. 9
      src/components/popups/newMedia.ts
  7. 22
      src/components/scrollable.ts
  8. 67
      src/helpers/fastSmoothScroll.ts
  9. 10
      src/layer.d.ts
  10. 22
      src/lib/appManagers/apiUpdatesManager.ts
  11. 38
      src/lib/appManagers/appDialogsManager.ts
  12. 187
      src/lib/appManagers/appDraftsManager.ts
  13. 10
      src/lib/appManagers/appImManager.ts
  14. 137
      src/lib/appManagers/appMessagesManager.ts
  15. 7
      src/lib/appManagers/appStateManager.ts
  16. 2
      src/lib/langPack.ts
  17. 4
      src/lib/mtproto/mtproto.worker.ts
  18. 6
      src/lib/mtproto/mtprotoworker.ts
  19. 4
      src/lib/mtproto/networker.ts
  20. 4
      src/lib/mtproto/networkerFactory.ts
  21. 7
      src/lib/rootScope.ts
  22. 39
      src/lib/storages/dialogs.ts
  23. 4
      src/scripts/generate_mtproto_types.js
  24. 12
      src/scripts/in/schema_additional_params.json

61
src/components/chat/bubbles.ts

@ -383,7 +383,7 @@ export default class ChatBubbles { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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);
});

44
src/components/chat/chat.ts

@ -11,6 +11,8 @@ import type { AppStickersManager } from "../../lib/appManagers/appStickersManage @@ -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<{ @@ -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<{ @@ -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<{ @@ -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<{ @@ -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<{ @@ -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<{ @@ -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<{ @@ -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();

97
src/components/chat/input.ts

@ -4,6 +4,8 @@ import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManage @@ -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"; @@ -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'; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -896,6 +954,8 @@ export default class ChatInput {
}
}
this.saveDraftDebounced();
this.updateSendBtn();
};
@ -1039,9 +1099,9 @@ export default class ChatInput { @@ -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 { @@ -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 { @@ -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 { @@ -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(() => {

2
src/components/chat/pinnedMessage.ts

@ -542,7 +542,7 @@ export default class ChatPinnedMessage { @@ -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);

2
src/components/chat/topbar.ts

@ -208,7 +208,7 @@ export default class ChatTopbar { @@ -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');

9
src/components/popups/newMedia.ts

@ -168,7 +168,8 @@ export default class PopupNewMedia extends PopupElement { @@ -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 { @@ -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 { @@ -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 = '';

22
src/components/scrollable.ts

@ -1,7 +1,6 @@ @@ -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 { @@ -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 { @@ -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';

67
src/helpers/fastSmoothScroll.ts

@ -104,6 +104,8 @@ function scrollWithJs( @@ -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( @@ -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( @@ -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( @@ -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);
}

10
src/layer.d.ts vendored

@ -831,7 +831,8 @@ export namespace Message { @@ -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 { @@ -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 { @@ -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
};
}

22
src/lib/appManagers/apiUpdatesManager.ts

@ -137,12 +137,15 @@ export class ApiUpdatesManager { @@ -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 { @@ -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 { @@ -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 { @@ -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

38
src/lib/appManagers/appDialogsManager.ts

@ -24,6 +24,7 @@ import Button from "../../components/button"; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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

187
src/lib/appManagers/appDraftsManager.ts

@ -0,0 +1,187 @@ @@ -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;

10
src/lib/appManagers/appImManager.ts

@ -30,6 +30,8 @@ import ChatDragAndDrop from '../../components/chat/dragAndDrop'; @@ -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 { @@ -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 { @@ -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 { @@ -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);

137
src/lib/appManagers/appMessagesManager.ts

@ -24,7 +24,7 @@ import DialogsStorage from "../storages/dialogs"; @@ -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"; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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

7
src/lib/appManagers/appStateManager.ts

@ -11,6 +11,7 @@ import type { AuthState } from '../../types'; @@ -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<{ @@ -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 = { @@ -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>;

2
src/lib/langPack.ts

@ -9,7 +9,7 @@ export const langPack: {[actionType: string]: string} = { @@ -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",

4
src/lib/mtproto/mtproto.worker.ts

@ -56,8 +56,8 @@ export const isWebpSupported = () => { @@ -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) => {

6
src/lib/mtproto/mtprotoworker.ts

@ -46,7 +46,7 @@ export class ApiManagerProxy extends CryptoWorkerMethods { @@ -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 { @@ -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 { @@ -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;
}

4
src/lib/mtproto/networker.ts

@ -1262,7 +1262,7 @@ export default class MTPNetworker { @@ -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 { @@ -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;
}

4
src/lib/mtproto/networkerFactory.ts

@ -3,10 +3,10 @@ import { ConnectionStatusChange, InvokeApiOptions } from "../../types"; @@ -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;
}

7
src/lib/rootScope.ts

@ -5,14 +5,17 @@ import type { Poll, PollResults } from "./appManagers/appPollsManager"; @@ -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 = { @@ -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 = { @@ -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

39
src/lib/storages/dialogs.ts

@ -1,7 +1,7 @@ @@ -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 { @@ -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 = (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;
let topDate = 0;
if(dialog.pFlags.pinned && !justReturn) {
topDate = this.generateDialogPinnedDate(dialog);
} else {
if(!message) {
message = this.appMessagesManager.getMessageByPeer(dialog.peerId, dialog.top_message);
}
}
const savedDraft: any = {};// DraftsManager.saveDraft(peerId, dialog.draft); // warning
if(savedDraft && savedDraft.date > topDate) {
topDate = savedDraft.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;
}
}
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);

4
src/scripts/generate_mtproto_types.js

@ -255,4 +255,6 @@ for(const method in methodsMap) { @@ -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);
const writePathTo = (path || __dirname + '/out/') + 'layer.d.ts';
console.log('Writing layer to:', writePathTo);
require('fs').writeFileSync(writePathTo, out);

12
src/scripts/in/schema_additional_params.json

@ -70,7 +70,8 @@ @@ -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 @@ @@ -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 @@ @@ -228,4 +230,10 @@
"params": [
{"name": "phone_number", "type": "string"}
]
}, {
"predicate": "draftMessage",
"params": [
{"name": "rReply", "type": "string"},
{"name": "rMessage", "type": "string"}
]
}]
Loading…
Cancel
Save