From 8992a6e7af2d21c12d5767bed245b9cd34ba948a Mon Sep 17 00:00:00 2001 From: morethanwords Date: Fri, 18 Sep 2020 18:03:26 +0300 Subject: [PATCH] Fix displaying chat from the archive in main list --- src/components/chat/audio.ts | 80 ++++ src/components/chat/contextMenu.ts | 167 +++++++ .../{chatInput.ts => chat/input.ts} | 32 +- src/components/chat/search.ts | 187 ++++++++ src/global.d.ts | 3 - src/layer.d.ts | 9 +- src/lib/appManagers/appDialogsManager.ts | 4 +- src/lib/appManagers/appImManager.ts | 438 +----------------- src/lib/appManagers/appMessagesManager.ts | 145 +++--- src/lib/appManagers/appPeersManager.ts | 89 ++-- src/lib/appManagers/appPhotosManager.ts | 2 +- src/lib/appManagers/appSidebarRight.ts | 2 +- src/lib/utils.ts | 36 +- src/scripts/in/schema_additional_params.json | 13 + tsconfig.json | 6 +- 15 files changed, 635 insertions(+), 578 deletions(-) create mode 100644 src/components/chat/audio.ts create mode 100644 src/components/chat/contextMenu.ts rename src/components/{chatInput.ts => chat/input.ts} (96%) create mode 100644 src/components/chat/search.ts diff --git a/src/components/chat/audio.ts b/src/components/chat/audio.ts new file mode 100644 index 00000000..e063a3b6 --- /dev/null +++ b/src/components/chat/audio.ts @@ -0,0 +1,80 @@ +import appImManager from "../../lib/appManagers/appImManager"; +import appMessagesManager from "../../lib/appManagers/appMessagesManager"; +import appPeersManager from "../../lib/appManagers/appPeersManager"; +import { RichTextProcessor } from "../../lib/richtextprocessor"; +import { cancelEvent, $rootScope } from "../../lib/utils"; +import appMediaPlaybackController from "../appMediaPlaybackController"; +import { formatDate } from "../wrappers"; + +export class ChatAudio { + public container: HTMLElement; + private toggle: HTMLElement; + private title: HTMLElement; + private subtitle: HTMLElement; + private close: HTMLElement; + + constructor() { + this.container = document.createElement('div'); + this.container.classList.add('pinned-audio', 'pinned-container'); + this.container.style.display = 'none'; + + this.toggle = document.createElement('div'); + this.toggle.classList.add('pinned-audio-ico', 'tgico'); + + this.title = document.createElement('div'); + this.title.classList.add('pinned-audio-title'); + + this.subtitle = document.createElement('div'); + this.subtitle.classList.add('pinned-audio-subtitle'); + + this.close = document.createElement('button'); + this.close.classList.add('pinned-audio-close', 'btn-icon', 'tgico-close'); + + this.container.append(this.toggle, this.title, this.subtitle, this.close); + + this.close.addEventListener('click', (e) => { + cancelEvent(e); + this.container.style.display = 'none'; + this.container.parentElement.classList.remove('is-audio-shown'); + if(this.toggle.classList.contains('flip-icon')) { + appMediaPlaybackController.toggle(); + } + }); + + this.toggle.addEventListener('click', (e) => { + cancelEvent(e); + appMediaPlaybackController.toggle(); + }); + + $rootScope.$on('audio_play', (e: CustomEvent) => { + const {doc, mid} = e.detail; + + let title: string, subtitle: string; + if(doc.type == 'voice' || doc.type == 'round') { + const message = appMessagesManager.getMessage(mid); + title = appPeersManager.getPeerTitle(message.fromID, false, true); + //subtitle = 'Voice message'; + subtitle = formatDate(message.date, false, false); + } else { + title = doc.audioTitle || doc.file_name; + subtitle = doc.audioPerformer ? RichTextProcessor.wrapPlainText(doc.audioPerformer) : 'Unknown Artist'; + } + + this.title.innerHTML = title; + this.subtitle.innerHTML = subtitle; + this.toggle.classList.add('flip-icon'); + + this.container.dataset.mid = '' + mid; + if(this.container.style.display) { + const scrollTop = appImManager.scrollable.scrollTop; + this.container.style.display = ''; + this.container.parentElement.classList.add('is-audio-shown'); + appImManager.scrollable.scrollTop = scrollTop; + } + }); + + $rootScope.$on('audio_pause', () => { + this.toggle.classList.remove('flip-icon'); + }); + } +} \ No newline at end of file diff --git a/src/components/chat/contextMenu.ts b/src/components/chat/contextMenu.ts new file mode 100644 index 00000000..ed12b1a1 --- /dev/null +++ b/src/components/chat/contextMenu.ts @@ -0,0 +1,167 @@ +import appChatsManager from "../../lib/appManagers/appChatsManager"; +import appImManager from "../../lib/appManagers/appImManager"; +import appMessagesManager from "../../lib/appManagers/appMessagesManager"; +import appPeersManager from "../../lib/appManagers/appPeersManager"; +import { findUpClassName, $rootScope } from "../../lib/utils"; +import appForward from "../appForward"; +import { parseMenuButtonsTo, attachContextMenuListener, positionMenu, openBtnMenu } from "../misc"; +import { PopupButton, PopupPeer } from "../popup"; + +export class ChatContextMenu { + private element = document.getElementById('bubble-contextmenu') as HTMLDivElement; + private buttons: { + reply: HTMLButtonElement, + edit: HTMLButtonElement, + copy: HTMLButtonElement, + pin: HTMLButtonElement, + forward: HTMLButtonElement, + delete: HTMLButtonElement + } = {} as any; + public msgID: number; + + constructor(private attachTo: HTMLElement) { + parseMenuButtonsTo(this.buttons, this.element.children); + + attachContextMenuListener(attachTo, (e) => { + let bubble: HTMLElement = null; + + try { + bubble = findUpClassName(e.target, 'bubble__container'); + } catch(e) {} + + if(!bubble) return; + + if(e instanceof MouseEvent) e.preventDefault(); + if(this.element.classList.contains('active')) { + return false; + } + if(e instanceof MouseEvent) e.cancelBubble = true; + + bubble = bubble.parentElement as HTMLDivElement; // bc container + + let msgID = +bubble.dataset.mid; + if(!msgID) return; + + let peerID = $rootScope.selectedPeerID; + this.msgID = msgID; + + const message = appMessagesManager.getMessage(msgID); + + this.buttons.copy.style.display = message.message ? '' : 'none'; + + if($rootScope.myID == peerID || (peerID < 0 && appChatsManager.hasRights(-peerID, 'pin'))) { + this.buttons.pin.style.display = ''; + } else { + this.buttons.pin.style.display = 'none'; + } + + this.buttons.edit.style.display = appMessagesManager.canEditMessage(msgID) ? '' : 'none'; + + let side: 'left' | 'right' = bubble.classList.contains('is-in') ? 'left' : 'right'; + positionMenu(e, this.element, side); + openBtnMenu(this.element); + + /////this.log('contextmenu', e, bubble, msgID, side); + }); + + this.buttons.copy.addEventListener('click', () => { + let message = appMessagesManager.getMessage(this.msgID); + + let str = message ? message.message : ''; + + var textArea = document.createElement("textarea"); + textArea.value = str; + textArea.style.position = "fixed"; //avoid scrolling to bottom + document.body.appendChild(textArea); + textArea.focus(); + textArea.select(); + + try { + document.execCommand('copy'); + } catch (err) { + console.error('Oops, unable to copy', err); + } + + document.body.removeChild(textArea); + }); + + this.buttons.delete.addEventListener('click', () => { + let peerID = $rootScope.selectedPeerID; + let firstName = appPeersManager.getPeerTitle(peerID, false, true); + + let callback = (revoke: boolean) => { + appMessagesManager.deleteMessages([this.msgID], revoke); + }; + + let title: string, description: string, buttons: PopupButton[]; + title = 'Delete Message?'; + description = `Are you sure you want to delete this message?`; + + if(peerID == $rootScope.myID) { + buttons = [{ + text: 'DELETE', + isDanger: true, + callback: () => callback(false) + }]; + } else { + buttons = [{ + text: 'DELETE JUST FOR ME', + isDanger: true, + callback: () => callback(false) + }]; + + if(peerID > 0) { + buttons.push({ + text: 'DELETE FOR ME AND ' + firstName, + isDanger: true, + callback: () => callback(true) + }); + } else if(appChatsManager.hasRights(-peerID, 'deleteRevoke')) { + buttons.push({ + text: 'DELETE FOR ALL', + isDanger: true, + callback: () => callback(true) + }); + } + } + + buttons.push({ + text: 'CANCEL', + isCancel: true + }); + + let popup = new PopupPeer('popup-delete-chat', { + peerID: peerID, + title: title, + description: description, + buttons: buttons + }); + + popup.show(); + }); + + this.buttons.reply.addEventListener('click', () => { + const message = appMessagesManager.getMessage(this.msgID); + const chatInputC = appImManager.chatInputC; + chatInputC.setTopInfo(appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message); + chatInputC.replyToMsgID = this.msgID; + chatInputC.editMsgID = 0; + }); + + this.buttons.forward.addEventListener('click', () => { + appForward.init([this.msgID]); + }); + + this.buttons.edit.addEventListener('click', () => { + const message = appMessagesManager.getMessage(this.msgID); + const chatInputC = appImManager.chatInputC; + chatInputC.setTopInfo('Editing', message.message, message.message, message); + chatInputC.replyToMsgID = 0; + chatInputC.editMsgID = this.msgID; + }); + + this.buttons.pin.addEventListener('click', () => { + appMessagesManager.updatePinnedMessage($rootScope.selectedPeerID, this.msgID); + }); + } +} \ No newline at end of file diff --git a/src/components/chatInput.ts b/src/components/chat/input.ts similarity index 96% rename from src/components/chatInput.ts rename to src/components/chat/input.ts index 70044521..c78895c4 100644 --- a/src/components/chatInput.ts +++ b/src/components/chat/input.ts @@ -1,19 +1,19 @@ -import Scrollable from "./scrollable_new"; -import { RichTextProcessor } from "../lib/richtextprocessor"; -import apiManager from "../lib/mtproto/mtprotoworker"; -import appWebPagesManager from "../lib/appManagers/appWebPagesManager"; -import appImManager from "../lib/appManagers/appImManager"; -import { getRichValue, calcImageInBox, cancelEvent } from "../lib/utils"; -import { wrapDocument, wrapReply } from "./wrappers"; -import appMessagesManager from "../lib/appManagers/appMessagesManager"; -import { Layouter, RectPart } from "./groupedLayout"; -import Recorder from '../../public/recorder.min'; +import Scrollable from "../scrollable_new"; +import { RichTextProcessor } from "../../lib/richtextprocessor"; +import apiManager from "../../lib/mtproto/mtprotoworker"; +import appWebPagesManager from "../../lib/appManagers/appWebPagesManager"; +import appImManager from "../../lib/appManagers/appImManager"; +import { getRichValue, calcImageInBox, cancelEvent } from "../../lib/utils"; +import { wrapDocument, wrapReply } from "../wrappers"; +import appMessagesManager from "../../lib/appManagers/appMessagesManager"; +import { Layouter, RectPart } from "../groupedLayout"; +import Recorder from '../../../public/recorder.min'; //import Recorder from '../opus-recorder/dist/recorder.min'; -import opusDecodeController from "../lib/opusDecodeController"; -import { touchSupport } from "../lib/config"; -import appDocsManager from "../lib/appManagers/appDocsManager"; -import emoticonsDropdown from "./emoticonsDropdown"; -import PopupCreatePoll from "./popupCreatePoll"; +import opusDecodeController from "../../lib/opusDecodeController"; +import { touchSupport } from "../../lib/config"; +import appDocsManager from "../../lib/appManagers/appDocsManager"; +import emoticonsDropdown from "../emoticonsDropdown"; +import PopupCreatePoll from "../popupCreatePoll"; export class ChatInput { public pageEl = document.getElementById('page-chats') as HTMLDivElement; @@ -729,7 +729,7 @@ export class ChatInput { public sendMessageWithDocument(document: any) { document = appDocsManager.getDoc(document); - if(document._ != 'documentEmpty') { + if(document && document._ != 'documentEmpty') { appMessagesManager.sendFile(appImManager.peerID, document, {isMedia: true, replyToMsgID: this.replyToMsgID}); this.onMessageSent(false, true); diff --git a/src/components/chat/search.ts b/src/components/chat/search.ts new file mode 100644 index 00000000..2e45e6a4 --- /dev/null +++ b/src/components/chat/search.ts @@ -0,0 +1,187 @@ +import appImManager from "../../lib/appManagers/appImManager"; +import { $rootScope, cancelEvent, whichChild, findUpTag } from "../../lib/utils"; +import AppSearch, { SearchGroup } from "../appSearch"; +import PopupDatePicker from "../popupDatepicker"; +import { ripple } from "../ripple"; +import SearchInput from "../searchInput"; + +export class ChatSearch { + private element: HTMLElement; + private backBtn: HTMLElement; + private searchInput: SearchInput; + + private results: HTMLElement; + + private footer: HTMLElement; + private dateBtn: HTMLElement; + private foundCountEl: HTMLElement; + private controls: HTMLElement; + private downBtn: HTMLElement; + private upBtn: HTMLElement; + + private appSearch: AppSearch; + private searchGroup: SearchGroup; + + private foundCount = 0; + private selectedIndex = 0; + private setPeerPromise: Promise; + + constructor() { + this.element = document.createElement('div'); + this.element.classList.add('sidebar-header', 'chat-search', 'chats-container'); + + this.backBtn = document.createElement('button'); + this.backBtn.classList.add('btn-icon', 'tgico-back', 'sidebar-close-button'); + ripple(this.backBtn); + + this.backBtn.addEventListener('click', () => { + appImManager.topbar.classList.remove('hide-pinned'); + this.element.remove(); + this.searchInput.remove(); + this.results.remove(); + this.footer.remove(); + this.footer.removeEventListener('click', this.onFooterClick); + this.dateBtn.removeEventListener('click', this.onDateClick); + this.upBtn.removeEventListener('click', this.onUpClick); + this.downBtn.removeEventListener('click', this.onDownClick); + this.searchGroup.list.removeEventListener('click', this.onResultsClick); + appImManager.bubblesContainer.classList.remove('search-results-active'); + }, {once: true}); + + this.searchInput = new SearchInput('Search'); + + // Results + this.results = document.createElement('div'); + this.results.classList.add('chat-search-results', 'chats-container'); + + this.searchGroup = new SearchGroup('', 'messages', undefined, '', false); + this.searchGroup.list.addEventListener('click', this.onResultsClick); + + this.appSearch = new AppSearch(this.results, this.searchInput, { + messages: this.searchGroup + }, (count) => { + this.foundCount = count; + + if(!this.foundCount) { + this.foundCountEl.innerText = this.searchInput.value ? 'No results' : ''; + this.results.classList.remove('active'); + appImManager.bubblesContainer.classList.remove('search-results-active'); + this.upBtn.setAttribute('disabled', 'true'); + this.downBtn.setAttribute('disabled', 'true'); + } else { + this.selectResult(this.searchGroup.list.children[0] as HTMLElement); + } + }); + this.appSearch.beginSearch($rootScope.selectedPeerID); + + //appImManager.topbar.parentElement.insertBefore(this.results, appImManager.bubblesContainer); + appImManager.bubblesContainer.append(this.results); + + // Footer + this.footer = document.createElement('div'); + this.footer.classList.add('chat-search-footer'); + + this.footer.addEventListener('click', this.onFooterClick); + ripple(this.footer); + + this.foundCountEl = document.createElement('span'); + this.foundCountEl.classList.add('chat-search-count'); + + this.dateBtn = document.createElement('button'); + this.dateBtn.classList.add('btn-icon', 'tgico-calendar'); + + this.controls = document.createElement('div'); + this.controls.classList.add('chat-search-controls'); + + this.upBtn = document.createElement('button'); + this.upBtn.classList.add('btn-icon', 'tgico-up'); + this.downBtn = document.createElement('button'); + this.downBtn.classList.add('btn-icon', 'tgico-down'); + + this.upBtn.setAttribute('disabled', 'true'); + this.downBtn.setAttribute('disabled', 'true'); + + this.dateBtn.addEventListener('click', this.onDateClick); + this.upBtn.addEventListener('click', this.onUpClick); + this.downBtn.addEventListener('click', this.onDownClick); + this.controls.append(this.upBtn, this.downBtn); + + this.footer.append(this.foundCountEl, this.dateBtn, this.controls); + + appImManager.topbar.parentElement.insertBefore(this.footer, appImManager.chatInput); + + // Append container + this.element.append(this.backBtn, this.searchInput.container); + + appImManager.topbar.classList.add('hide-pinned'); + appImManager.topbar.parentElement.append(this.element); + + this.searchInput.input.focus(); + } + + onDateClick = (e: MouseEvent) => { + cancelEvent(e); + new PopupDatePicker(new Date(), appImManager.onDatePick).show(); + }; + + selectResult = (elem: HTMLElement) => { + if(this.setPeerPromise) return this.setPeerPromise; + + const peerID = +elem.getAttribute('data-peerID'); + const lastMsgID = +elem.dataset.mid || undefined; + + const index = whichChild(elem); + + if(index == (this.foundCount - 1)) { + this.upBtn.setAttribute('disabled', 'true'); + } else { + this.upBtn.removeAttribute('disabled'); + } + + if(!index) { + this.downBtn.setAttribute('disabled', 'true'); + } else { + this.downBtn.removeAttribute('disabled'); + } + + this.results.classList.remove('active'); + appImManager.bubblesContainer.classList.remove('search-results-active'); + + const res = appImManager.setPeer(peerID, lastMsgID); + this.setPeerPromise = (res instanceof Promise ? res : Promise.resolve(res)).then(() => { + this.selectedIndex = index; + this.foundCountEl.innerText = `${index + 1} of ${this.foundCount}`; + + const renderedCount = this.searchGroup.list.childElementCount; + if(this.selectedIndex >= (renderedCount - 6)) { + this.appSearch.searchMore(); + } + }).finally(() => { + this.setPeerPromise = null; + }); + }; + + onResultsClick = (e: MouseEvent) => { + const target = findUpTag(e.target, 'LI'); + if(target) { + this.selectResult(target); + } + }; + + onFooterClick = (e: MouseEvent) => { + if(this.foundCount) { + appImManager.bubblesContainer.classList.toggle('search-results-active'); + this.results.classList.toggle('active'); + } + }; + + onUpClick = (e: MouseEvent) => { + cancelEvent(e); + this.selectResult(this.searchGroup.list.children[this.selectedIndex + 1] as HTMLElement); + }; + + onDownClick = (e: MouseEvent) => { + cancelEvent(e); + this.selectResult(this.searchGroup.list.children[this.selectedIndex - 1] as HTMLElement); + }; +} \ No newline at end of file diff --git a/src/global.d.ts b/src/global.d.ts index 7b7e364d..c5121e17 100644 --- a/src/global.d.ts +++ b/src/global.d.ts @@ -5,6 +5,3 @@ declare module 'worker-loader!*' { export default WebpackWorker; } - -declare function setInterval(callback: (...args: any[]) => void, ms: number): number; -declare function setTimeout(callback: (...args: any[]) => void, ms: number): number; \ No newline at end of file diff --git a/src/layer.d.ts b/src/layer.d.ts index c184f8ee..bb3fd247 100644 --- a/src/layer.d.ts +++ b/src/layer.d.ts @@ -1076,7 +1076,9 @@ export namespace Dialog { notify_settings: PeerNotifySettings, pts?: number, draft?: DraftMessage, - folder_id?: number + folder_id?: number, + index?: number, + peerID?: number }; export type dialogFolder = { @@ -1091,7 +1093,10 @@ export namespace Dialog { unread_muted_peers_count: number, unread_unmuted_peers_count: number, unread_muted_messages_count: number, - unread_unmuted_messages_count: number + unread_unmuted_messages_count: number, + index?: number, + peerID?: number, + folder_id?: number }; } diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index 4747dce4..b768619d 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -733,7 +733,7 @@ export class AppDialogsManager { this.setListClickListener(ul, null, true); if(!this.showFiltersTimeout) { - this.showFiltersTimeout = setTimeout(() => { + this.showFiltersTimeout = window.setTimeout(() => { this.showFiltersTimeout = 0; this.folders.menuScrollContainer.classList.remove('hide'); this.setFiltersUnreadCount(); @@ -1086,7 +1086,7 @@ export class AppDialogsManager { public accumulateArchivedUnread() { if(this.accumulateArchivedTimeout) return; - this.accumulateArchivedTimeout = setTimeout(() => { + this.accumulateArchivedTimeout = window.setTimeout(() => { this.accumulateArchivedTimeout = 0; const dialogs = appMessagesManager.dialogsStorage.getFolder(1); const sum = dialogs.reduce((acc, dialog) => acc + dialog.unread_count, 0); diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 73ba7539..1083e933 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -14,10 +14,10 @@ import { logger, LogLevels } from "../logger"; import appMediaViewer from "./appMediaViewer"; import appSidebarLeft from "./appSidebarLeft"; import appChatsManager, { Channel, Chat } from "./appChatsManager"; -import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply, wrapAlbum, wrapPoll, formatDate } from '../../components/wrappers'; +import { wrapDocument, wrapPhoto, wrapVideo, wrapSticker, wrapReply, wrapAlbum, wrapPoll } from '../../components/wrappers'; import ProgressivePreloader from '../../components/preloader'; -import { openBtnMenu, formatPhoneNumber, positionMenu, parseMenuButtonsTo, attachContextMenuListener } from '../../components/misc'; -import { ChatInput } from '../../components/chatInput'; +import { formatPhoneNumber, parseMenuButtonsTo } from '../../components/misc'; +import { ChatInput } from '../../components/chat/input'; //import Scrollable from '../../components/scrollable'; import Scrollable from '../../components/scrollable_new'; import BubbleGroups from '../../components/bubbleGroups'; @@ -28,19 +28,18 @@ import appStickersManager from './appStickersManager'; import AvatarElement from '../../components/avatar'; import appInlineBotsManager from './AppInlineBotsManager'; import StickyIntersector from '../../components/stickyIntersector'; -import { PopupButton, PopupPeer } from '../../components/popup'; import { mediaSizes, touchSupport, isAndroid, isApple } from '../config'; import animationIntersector from '../../components/animationIntersector'; import PopupStickers from '../../components/popupStickers'; -import SearchInput from '../../components/searchInput'; -import AppSearch, { SearchGroup } from '../../components/appSearch'; import PopupDatePicker from '../../components/popupDatepicker'; -import appMediaPlaybackController from '../../components/appMediaPlaybackController'; import appPollsManager from './appPollsManager'; import { ripple } from '../../components/ripple'; import { horizontalMenu } from '../../components/horizontalMenu'; import AudioElement from '../../components/audio'; import { InputNotifyPeer, InputPeerNotifySettings } from '../../layer'; +import { ChatAudio } from '../../components/chat/audio'; +import { ChatContextMenu } from '../../components/chat/contextMenu'; +import { ChatSearch } from '../../components/chat/search'; //console.log('appImManager included33!'); @@ -50,419 +49,6 @@ const testScroll = false; const ANIMATIONGROUP = 'chat'; -class ChatContextMenu { - private element = document.getElementById('bubble-contextmenu') as HTMLDivElement; - private buttons: { - reply: HTMLButtonElement, - edit: HTMLButtonElement, - copy: HTMLButtonElement, - pin: HTMLButtonElement, - forward: HTMLButtonElement, - delete: HTMLButtonElement - } = {} as any; - public msgID: number; - - constructor(private attachTo: HTMLElement) { - parseMenuButtonsTo(this.buttons, this.element.children); - - attachContextMenuListener(attachTo, (e) => { - let bubble: HTMLElement = null; - - try { - bubble = findUpClassName(e.target, 'bubble__container'); - } catch(e) {} - - if(!bubble) return; - - if(e instanceof MouseEvent) e.preventDefault(); - if(this.element.classList.contains('active')) { - return false; - } - if(e instanceof MouseEvent) e.cancelBubble = true; - - bubble = bubble.parentElement as HTMLDivElement; // bc container - - let msgID = +bubble.dataset.mid; - if(!msgID) return; - - let peerID = $rootScope.selectedPeerID; - this.msgID = msgID; - - const message = appMessagesManager.getMessage(msgID); - - this.buttons.copy.style.display = message.message ? '' : 'none'; - - if($rootScope.myID == peerID || (peerID < 0 && appChatsManager.hasRights(-peerID, 'pin'))) { - this.buttons.pin.style.display = ''; - } else { - this.buttons.pin.style.display = 'none'; - } - - this.buttons.edit.style.display = appMessagesManager.canEditMessage(msgID) ? '' : 'none'; - - let side: 'left' | 'right' = bubble.classList.contains('is-in') ? 'left' : 'right'; - positionMenu(e, this.element, side); - openBtnMenu(this.element); - - /////this.log('contextmenu', e, bubble, msgID, side); - }); - - this.buttons.copy.addEventListener('click', () => { - let message = appMessagesManager.getMessage(this.msgID); - - let str = message ? message.message : ''; - - var textArea = document.createElement("textarea"); - textArea.value = str; - textArea.style.position = "fixed"; //avoid scrolling to bottom - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - - try { - document.execCommand('copy'); - } catch (err) { - console.error('Oops, unable to copy', err); - } - - document.body.removeChild(textArea); - }); - - this.buttons.delete.addEventListener('click', () => { - let peerID = $rootScope.selectedPeerID; - let firstName = appPeersManager.getPeerTitle(peerID, false, true); - - let callback = (revoke: boolean) => { - appMessagesManager.deleteMessages([this.msgID], revoke); - }; - - let title: string, description: string, buttons: PopupButton[]; - title = 'Delete Message?'; - description = `Are you sure you want to delete this message?`; - - if(peerID == $rootScope.myID) { - buttons = [{ - text: 'DELETE', - isDanger: true, - callback: () => callback(false) - }]; - } else { - buttons = [{ - text: 'DELETE JUST FOR ME', - isDanger: true, - callback: () => callback(false) - }]; - - if(peerID > 0) { - buttons.push({ - text: 'DELETE FOR ME AND ' + firstName, - isDanger: true, - callback: () => callback(true) - }); - } else if(appChatsManager.hasRights(-peerID, 'deleteRevoke')) { - buttons.push({ - text: 'DELETE FOR ALL', - isDanger: true, - callback: () => callback(true) - }); - } - } - - buttons.push({ - text: 'CANCEL', - isCancel: true - }); - - let popup = new PopupPeer('popup-delete-chat', { - peerID: peerID, - title: title, - description: description, - buttons: buttons - }); - - popup.show(); - }); - - this.buttons.reply.addEventListener('click', () => { - const message = appMessagesManager.getMessage(this.msgID); - const chatInputC = appImManager.chatInputC; - chatInputC.setTopInfo(appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message); - chatInputC.replyToMsgID = this.msgID; - chatInputC.editMsgID = 0; - }); - - this.buttons.forward.addEventListener('click', () => { - appForward.init([this.msgID]); - }); - - this.buttons.edit.addEventListener('click', () => { - const message = appMessagesManager.getMessage(this.msgID); - const chatInputC = appImManager.chatInputC; - chatInputC.setTopInfo('Editing', message.message, message.message, message); - chatInputC.replyToMsgID = 0; - chatInputC.editMsgID = this.msgID; - }); - - this.buttons.pin.addEventListener('click', () => { - appMessagesManager.updatePinnedMessage($rootScope.selectedPeerID, this.msgID); - }); - } -} - -class ChatSearch { - private element: HTMLElement; - private backBtn: HTMLElement; - private searchInput: SearchInput; - - private results: HTMLElement; - - private footer: HTMLElement; - private dateBtn: HTMLElement; - private foundCountEl: HTMLElement; - private controls: HTMLElement; - private downBtn: HTMLElement; - private upBtn: HTMLElement; - - private appSearch: AppSearch; - private searchGroup: SearchGroup; - - private foundCount = 0; - private selectedIndex = 0; - private setPeerPromise: Promise; - - constructor() { - this.element = document.createElement('div'); - this.element.classList.add('sidebar-header', 'chat-search', 'chats-container'); - - this.backBtn = document.createElement('button'); - this.backBtn.classList.add('btn-icon', 'tgico-back', 'sidebar-close-button'); - ripple(this.backBtn); - - this.backBtn.addEventListener('click', () => { - appImManager.topbar.classList.remove('hide-pinned'); - this.element.remove(); - this.searchInput.remove(); - this.results.remove(); - this.footer.remove(); - this.footer.removeEventListener('click', this.onFooterClick); - this.dateBtn.removeEventListener('click', this.onDateClick); - this.upBtn.removeEventListener('click', this.onUpClick); - this.downBtn.removeEventListener('click', this.onDownClick); - this.searchGroup.list.removeEventListener('click', this.onResultsClick); - appImManager.bubblesContainer.classList.remove('search-results-active'); - }, {once: true}); - - this.searchInput = new SearchInput('Search'); - - // Results - this.results = document.createElement('div'); - this.results.classList.add('chat-search-results', 'chats-container'); - - this.searchGroup = new SearchGroup('', 'messages', undefined, '', false); - this.searchGroup.list.addEventListener('click', this.onResultsClick); - - this.appSearch = new AppSearch(this.results, this.searchInput, { - messages: this.searchGroup - }, (count) => { - this.foundCount = count; - - if(!this.foundCount) { - this.foundCountEl.innerText = this.searchInput.value ? 'No results' : ''; - this.results.classList.remove('active'); - appImManager.bubblesContainer.classList.remove('search-results-active'); - this.upBtn.setAttribute('disabled', 'true'); - this.downBtn.setAttribute('disabled', 'true'); - } else { - this.selectResult(this.searchGroup.list.children[0] as HTMLElement); - } - }); - this.appSearch.beginSearch($rootScope.selectedPeerID); - - //appImManager.topbar.parentElement.insertBefore(this.results, appImManager.bubblesContainer); - appImManager.bubblesContainer.append(this.results); - - // Footer - this.footer = document.createElement('div'); - this.footer.classList.add('chat-search-footer'); - - this.footer.addEventListener('click', this.onFooterClick); - ripple(this.footer); - - this.foundCountEl = document.createElement('span'); - this.foundCountEl.classList.add('chat-search-count'); - - this.dateBtn = document.createElement('button'); - this.dateBtn.classList.add('btn-icon', 'tgico-calendar'); - - this.controls = document.createElement('div'); - this.controls.classList.add('chat-search-controls'); - - this.upBtn = document.createElement('button'); - this.upBtn.classList.add('btn-icon', 'tgico-up'); - this.downBtn = document.createElement('button'); - this.downBtn.classList.add('btn-icon', 'tgico-down'); - - this.upBtn.setAttribute('disabled', 'true'); - this.downBtn.setAttribute('disabled', 'true'); - - this.dateBtn.addEventListener('click', this.onDateClick); - this.upBtn.addEventListener('click', this.onUpClick); - this.downBtn.addEventListener('click', this.onDownClick); - this.controls.append(this.upBtn, this.downBtn); - - this.footer.append(this.foundCountEl, this.dateBtn, this.controls); - - appImManager.topbar.parentElement.insertBefore(this.footer, appImManager.chatInput); - - // Append container - this.element.append(this.backBtn, this.searchInput.container); - - appImManager.topbar.classList.add('hide-pinned'); - appImManager.topbar.parentElement.append(this.element); - - this.searchInput.input.focus(); - } - - onDateClick = (e: MouseEvent) => { - cancelEvent(e); - new PopupDatePicker(new Date(), appImManager.onDatePick).show(); - }; - - selectResult = (elem: HTMLElement) => { - if(this.setPeerPromise) return this.setPeerPromise; - - const peerID = +elem.getAttribute('data-peerID'); - const lastMsgID = +elem.dataset.mid || undefined; - - const index = whichChild(elem); - - if(index == (this.foundCount - 1)) { - this.upBtn.setAttribute('disabled', 'true'); - } else { - this.upBtn.removeAttribute('disabled'); - } - - if(!index) { - this.downBtn.setAttribute('disabled', 'true'); - } else { - this.downBtn.removeAttribute('disabled'); - } - - this.results.classList.remove('active'); - appImManager.bubblesContainer.classList.remove('search-results-active'); - - const res = appImManager.setPeer(peerID, lastMsgID); - this.setPeerPromise = (res instanceof Promise ? res : Promise.resolve(res)).then(() => { - this.selectedIndex = index; - this.foundCountEl.innerText = `${index + 1} of ${this.foundCount}`; - - const renderedCount = this.searchGroup.list.childElementCount; - if(this.selectedIndex >= (renderedCount - 6)) { - this.appSearch.searchMore(); - } - }).finally(() => { - this.setPeerPromise = null; - }); - }; - - onResultsClick = (e: MouseEvent) => { - const target = findUpTag(e.target, 'LI'); - if(target) { - this.selectResult(target); - } - }; - - onFooterClick = (e: MouseEvent) => { - if(this.foundCount) { - appImManager.bubblesContainer.classList.toggle('search-results-active'); - this.results.classList.toggle('active'); - } - }; - - onUpClick = (e: MouseEvent) => { - cancelEvent(e); - this.selectResult(this.searchGroup.list.children[this.selectedIndex + 1] as HTMLElement); - }; - - onDownClick = (e: MouseEvent) => { - cancelEvent(e); - this.selectResult(this.searchGroup.list.children[this.selectedIndex - 1] as HTMLElement); - }; -} - -class ChatAudio { - public container: HTMLElement; - private toggle: HTMLElement; - private title: HTMLElement; - private subtitle: HTMLElement; - private close: HTMLElement; - - constructor() { - this.container = document.createElement('div'); - this.container.classList.add('pinned-audio', 'pinned-container'); - this.container.style.display = 'none'; - - this.toggle = document.createElement('div'); - this.toggle.classList.add('pinned-audio-ico', 'tgico'); - - this.title = document.createElement('div'); - this.title.classList.add('pinned-audio-title'); - - this.subtitle = document.createElement('div'); - this.subtitle.classList.add('pinned-audio-subtitle'); - - this.close = document.createElement('button'); - this.close.classList.add('pinned-audio-close', 'btn-icon', 'tgico-close'); - - this.container.append(this.toggle, this.title, this.subtitle, this.close); - - this.close.addEventListener('click', (e) => { - cancelEvent(e); - this.container.style.display = 'none'; - this.container.parentElement.classList.remove('is-audio-shown'); - if(this.toggle.classList.contains('flip-icon')) { - appMediaPlaybackController.toggle(); - } - }); - - this.toggle.addEventListener('click', (e) => { - cancelEvent(e); - appMediaPlaybackController.toggle(); - }); - - $rootScope.$on('audio_play', (e: CustomEvent) => { - const {doc, mid} = e.detail; - - let title: string, subtitle: string; - if(doc.type == 'voice' || doc.type == 'round') { - const message = appMessagesManager.getMessage(mid); - title = appPeersManager.getPeerTitle(message.fromID, false, true); - //subtitle = 'Voice message'; - subtitle = formatDate(message.date, false, false); - } else { - title = doc.audioTitle || doc.file_name; - subtitle = doc.audioPerformer ? RichTextProcessor.wrapPlainText(doc.audioPerformer) : 'Unknown Artist'; - } - - this.title.innerHTML = title; - this.subtitle.innerHTML = subtitle; - this.toggle.classList.add('flip-icon'); - - this.container.dataset.mid = '' + mid; - if(this.container.style.display) { - const scrollTop = appImManager.scrollable.scrollTop; - this.container.style.display = ''; - this.container.parentElement.classList.add('is-audio-shown'); - appImManager.scrollable.scrollTop = scrollTop; - } - }); - - $rootScope.$on('audio_pause', () => { - this.toggle.classList.remove('flip-icon'); - }); - } -} - export class AppImManager { public columnEl = document.getElementById('column-center') as HTMLDivElement; public btnJoin = this.columnEl.querySelector('.chat-join') as HTMLButtonElement; @@ -651,14 +237,14 @@ export class AppImManager { if(message.media) { if(message.media.photo) { const photo = appPhotosManager.getPhoto(tempID); - //if(photo._ != 'photoEmpty') { + if(/* photo._ != 'photoEmpty' */photo) { const newPhoto = message.media.photo; newPhoto.downloaded = photo.downloaded; newPhoto.url = photo.url; - //} + } } else if(message.media.document) { const doc = appDocsManager.getDoc(tempID); - if(/* doc._ != 'documentEmpty' && */doc.type && doc.type != 'sticker') { + if(/* doc._ != 'documentEmpty' && */doc?.type && doc.type != 'sticker') { const newDoc = message.media.document; newDoc.downloaded = doc.downloaded; newDoc.url = doc.url; @@ -1223,7 +809,7 @@ export class AppImManager { this.chatInner.classList.add('is-scrolling'); } - this.isScrollingTimeout = setTimeout(() => { + this.isScrollingTimeout = window.setTimeout(() => { this.chatInner.classList.remove('is-scrolling'); this.isScrollingTimeout = 0; }, 1350); @@ -1283,7 +869,7 @@ export class AppImManager { clearTimeout(this.isScrollingTimeout); } - this.isScrollingTimeout = setTimeout(() => { + this.isScrollingTimeout = window.setTimeout(() => { this.chatInner.classList.remove('is-scrolling'); this.isScrollingTimeout = 0; }, 1350); @@ -3035,7 +2621,7 @@ export class AppImManager { } } - this.typingTimeouts[peerID] = setTimeout(() => { + this.typingTimeouts[peerID] = window.setTimeout(() => { this.typingTimeouts[peerID] = 0; delete this.typingUsers[update.user_id]; diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 676ea5db..c8744c98 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -24,9 +24,10 @@ import { Modify } from "../../types"; import { logger, LogLevels } from "../logger"; import type {ApiFileManager} from '../mtproto/apiFileManager'; import appDownloadManager from "./appDownloadManager"; -import { DialogFilter, InputDialogPeer, InputMessage, MethodDeclMap, MessagesFilter, PhotoSize } from "../../layer"; +import { DialogFilter, InputDialogPeer, InputMessage, MethodDeclMap, MessagesFilter, PhotoSize, DocumentAttribute, Dialog as MTDialog, MessagesDialogs, MessagesPeerDialogs } from "../../layer"; //console.trace('include'); +// TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет const APITIMEOUT = 0; @@ -47,27 +48,7 @@ export type HistoryResult = { unreadSkip: boolean }; -export type Dialog = { - _: 'dialog', - top_message: number, - read_inbox_max_id: number, - read_outbox_max_id: number, - peer: any, - notify_settings: any, - folder_id: number, - flags: number, - draft: any, - unread_count: number, - unread_mentions_count: number, - - index: number, - peerID: number, - pFlags: Partial<{ - pinned: true, - unread_mark: true - }>, - pts: number -} +export type Dialog = MTDialog.dialog; export class DialogsStorage { public dialogs: {[peerID: string]: Dialog} = {}; @@ -487,7 +468,7 @@ export class AppMessagesManager { public pendingByRandomID: {[randomID: string]: [number, number]} = {}; public pendingByMessageID: any = {}; public pendingAfterMsgs: any = {}; - public pendingTopMsgs: any = {}; + public pendingTopMsgs: {[peerID: string]: number} = {}; public sendFilePromise: CancellablePromise = Promise.resolve(); public tempID = -1; public tempFinalizeCallbacks: any = {}; @@ -1009,7 +990,7 @@ export class AppMessagesManager { attributes.push({_: 'documentAttributeFilename', file_name: fileName || apiFileName}); if(['document', 'video', 'audio', 'voice'].indexOf(attachType) !== -1 && !isDocument) { - let doc: any = { + let doc: MyDocument = { _: 'document', id: '' + messageID, duration: options.duration, @@ -1021,7 +1002,7 @@ export class AppMessagesManager { mime_type: fileType, url: options.objectURL || '', size: file.size - }; + } as any; appDocsManager.saveDoc(doc); } @@ -1297,21 +1278,18 @@ export class AppMessagesManager { if(file.type.indexOf('video/') === 0) { let flags = 1; - let videoAttribute = { + let videoAttribute: DocumentAttribute.documentAttributeVideo = { _: 'documentAttributeVideo', flags: flags, pFlags: { // that's only for client, not going to telegram - supports_streaming: true, - round_message: false - }, - round_message: false, - supports_streaming: true, + supports_streaming: true + }, duration: details.duration, w: details.width, h: details.height }; - let doc: any = { + let doc: MyDocument = { _: 'document', id: '' + messageID, attributes: [videoAttribute], @@ -1320,7 +1298,7 @@ export class AppMessagesManager { mime_type: file.type, url: details.objectURL || '', size: file.size - }; + } as any; appDocsManager.saveDoc(doc); media.document = doc; @@ -1894,9 +1872,11 @@ export class AppMessagesManager { hash: 0 }, { timeout: APITIMEOUT - }).then((dialogsResult: any) => { + }).then((dialogsResult) => { ///////this.log('messages.getDialogs result:', dialogsResult); + if(dialogsResult._ == 'messages.dialogsNotModified') return null; + if(!offsetDate) { telegramMeWebService.setAuthorized(true); } @@ -1905,21 +1885,31 @@ export class AppMessagesManager { appChatsManager.saveApiChats(dialogsResult.chats); this.saveMessages(dialogsResult.messages); - var maxSeenIdIncremented = offsetDate ? true : false; - var hasPrepend = false; - let length = dialogsResult.dialogs.length; - let noIDsDialogs: any = {}; - for(let i = length - 1; i >= 0; --i) { - let dialog = dialogsResult.dialogs[i]; + let maxSeenIdIncremented = offsetDate ? true : false; + let hasPrepend = false; + let noIDsDialogs: {[peerID: number]: Dialog} = {}; + (dialogsResult.dialogs as Dialog[]).forEachReverse(dialog => { + //const d = Object.assign({}, dialog); + // ! нужно передавать folderID, так как по папке != 0 нет свойства folder_id + this.saveConversation(dialog, folderID); + + /* if(dialog.peerID == 239602833) { + this.log.error('lun bot', folderID, d); + } */ - this.saveConversation(dialog); if(offsetIndex && dialog.index > offsetIndex) { this.newDialogsToHandle[dialog.peerID] = dialog; hasPrepend = true; } + // ! это может случиться, если запрос идёт не по папке 0, а по 1. почему-то read'ов нет + // ! в итоге, чтобы получить 1 диалог, делается первый запрос по папке 0, потом запрос для архивных по папке 1, и потом ещё перезагрузка архивного диалога if(!dialog.read_inbox_max_id && !dialog.read_outbox_max_id) { noIDsDialogs[dialog.peerID] = dialog; + + /* if(dialog.peerID == 239602833) { + this.log.error('lun bot', folderID); + } */ } if(!maxSeenIdIncremented && @@ -1927,7 +1917,7 @@ export class AppMessagesManager { this.incrementMaxSeenID(dialog.top_message); maxSeenIdIncremented = true; } - } + }); if(Object.keys(noIDsDialogs).length) { //setTimeout(() => { // test bad situation @@ -1941,9 +1931,11 @@ export class AppMessagesManager { //}, 10e3); } + const count = (dialogsResult as MessagesDialogs.messagesDialogsSlice).count; + if(!dialogsResult.dialogs.length || - !dialogsResult.count || - dialogs.length >= dialogsResult.count) { + !count || + dialogs.length >= count) { this.dialogsStorage.allDialogsLoaded[folderID] = true; } @@ -1953,7 +1945,7 @@ export class AppMessagesManager { $rootScope.$broadcast('dialogs_multiupdate', {}); } - return dialogsResult.count; + return count; }); } @@ -2115,11 +2107,11 @@ export class AppMessagesManager { } if(justClear) { - $rootScope.$broadcast('dialog_flush', {peerID: peerID}); + $rootScope.$broadcast('dialog_flush', {peerID}); } else { this.dialogsStorage.dropDialog(peerID); - $rootScope.$broadcast('dialog_drop', {peerID: peerID}); + $rootScope.$broadcast('dialog_drop', {peerID}); } }); } @@ -2598,7 +2590,9 @@ export class AppMessagesManager { return true; } - public applyConversations(dialogsResult: any) { + public applyConversations(dialogsResult: MessagesPeerDialogs.messagesPeerDialogs) { + // * В эту функцию попадут только те диалоги, в которых есть read_inbox_max_id и read_outbox_max_id, в отличие от тех, что будут в getTopMessages + appUsersManager.saveApiUsers(dialogsResult.users); appChatsManager.saveApiChats(dialogsResult.chats); this.saveMessages(dialogsResult.messages); @@ -2607,27 +2601,33 @@ export class AppMessagesManager { const updatedDialogs: {[peerID: number]: Dialog} = {}; let hasUpdated = false; - dialogsResult.dialogs.forEach((dialog: any) => { + (dialogsResult.dialogs as Dialog[]).forEach((dialog) => { const peerID = appPeersManager.getPeerID(dialog.peer); let topMessage = dialog.top_message; - const topPendingMesage = this.pendingTopMsgs[peerID]; - if(topPendingMesage) { - if(!topMessage || this.getMessage(topPendingMesage).date > this.getMessage(topMessage).date) { - dialog.top_message = topMessage = topPendingMesage; + const topPendingMessage = this.pendingTopMsgs[peerID]; + if(topPendingMessage) { + if(!topMessage || this.getMessage(topPendingMessage).date > this.getMessage(topMessage).date) { + dialog.top_message = topMessage = topPendingMessage; } } + /* const d = Object.assign({}, dialog); + if(peerID == 239602833) { + this.log.error('applyConversation lun', dialog, d); + } */ + if(topMessage) { const wasDialogBefore = this.getDialogByPeerID(peerID)[0]; // here need to just replace, not FULL replace dialog! WARNING - if(wasDialogBefore && wasDialogBefore.pFlags && wasDialogBefore.pFlags.pinned) { + /* if(wasDialogBefore?.pFlags?.pinned && !dialog?.pFlags?.pinned) { + this.log.error('here need to just replace, not FULL replace dialog! WARNING', wasDialogBefore, dialog); if(!dialog.pFlags) dialog.pFlags = {}; dialog.pFlags.pinned = true; - } + } */ this.saveConversation(dialog); - + if(wasDialogBefore) { $rootScope.$broadcast('dialog_top', dialog); } else { @@ -2656,11 +2656,16 @@ export class AppMessagesManager { } } - public saveConversation(dialog: Dialog) { + public saveConversation(dialog: Dialog, folderID = 0) { const peerID = appPeersManager.getPeerID(dialog.peer); if(!peerID) { return false; } + + if(dialog._ != 'dialog'/* || peerID == 239602833 */) { + console.error('saveConversation not regular dialog', dialog, Object.assign({}, dialog)); + } + const channelID = appPeersManager.isChannel(peerID) ? -peerID : 0; const peerText = appPeersManager.getPeerSearchText(peerID); searchIndexManager.indexObject(peerID, peerText, this.dialogsIndex); @@ -2700,7 +2705,14 @@ export class AppMessagesManager { dialog.read_inbox_max_id = appMessagesIDsManager.getFullMessageID(dialog.read_inbox_max_id, channelID); dialog.read_outbox_max_id = appMessagesIDsManager.getFullMessageID(dialog.read_outbox_max_id, channelID); - if(!dialog.hasOwnProperty('folder_id')) dialog.folder_id = 0; + if(!dialog.hasOwnProperty('folder_id')) { + if(dialog._ == 'dialog') { + dialog.folder_id = folderID; + }/* else if(dialog._ == 'dialogFolder') { + dialog.folder_id = dialog.folder.id; + } */ + } + dialog.peerID = peerID; this.dialogsStorage.generateIndexForDialog(dialog); @@ -3057,7 +3069,7 @@ export class AppMessagesManager { this.incrementMaxSeenID(newMaxSeenID); } - $rootScope.$broadcast('dialogs_multiupdate', this.newDialogsToHandle); + $rootScope.$broadcast('dialogs_multiupdate', this.newDialogsToHandle as any); this.newDialogsToHandle = {}; }; @@ -3502,7 +3514,7 @@ export class AppMessagesManager { if(!update.order) { apiManager.invokeApi('messages.getPinnedDialogs', { folder_id: folderID - }).then((dialogsResult: any) => { + }).then((dialogsResult) => { dialogsResult.dialogs.reverse(); this.applyConversations(dialogsResult); @@ -3750,8 +3762,9 @@ export class AppMessagesManager { } } - Object.keys(historiesUpdated).forEach(peerID => { - let updatedData = historiesUpdated[+peerID]; + Object.keys(historiesUpdated).forEach(_peerID => { + const peerID = +_peerID; + let updatedData = historiesUpdated[peerID]; let historyStorage = this.historiesStorage[peerID]; if(historyStorage !== undefined) { let newHistory: number[] = []; @@ -3778,22 +3791,22 @@ export class AppMessagesManager { } historyStorage.pending = newPending; - $rootScope.$broadcast('history_delete', {peerID: peerID, msgs: updatedData.msgs}); + $rootScope.$broadcast('history_delete', {peerID, msgs: updatedData.msgs}); } - let foundDialog = this.getDialogByPeerID(+peerID)[0]; + let foundDialog = this.getDialogByPeerID(peerID)[0]; if(foundDialog) { if(updatedData.unread) { foundDialog.unread_count -= updatedData.unread; $rootScope.$broadcast('dialog_unread', { - peerID: peerID, + peerID, count: foundDialog.unread_count }); } if(updatedData.msgs[foundDialog.top_message]) { - this.reloadConversation(+peerID); + this.reloadConversation(peerID); } } }); diff --git a/src/lib/appManagers/appPeersManager.ts b/src/lib/appManagers/appPeersManager.ts index f714573f..2897b76c 100644 --- a/src/lib/appManagers/appPeersManager.ts +++ b/src/lib/appManagers/appPeersManager.ts @@ -2,7 +2,7 @@ import appUsersManager from "./appUsersManager"; import appChatsManager from "./appChatsManager"; import { isObject } from "../utils"; import { RichTextProcessor } from "../richtextprocessor"; -import { InputPeer, InputDialogPeer } from "../../layer"; +import { InputPeer, InputDialogPeer, Peer } from "../../layer"; // https://github.com/eelcohn/Telegram-API/wiki/Calculating-color-for-a-Telegram-user-on-IRC /* @@ -20,30 +20,30 @@ const DialogColorsFg = ['#c03d33', '#4fad2d', '#d09306', '#168acd', '#8544d6', ' const DialogColors = ['#e17076', '#7bc862', '#e5ca77', '#65AADD', '#a695e7', '#ee7aae', '#6ec9cb', '#faa774']; const DialogColorsMap = [0, 7, 4, 1, 6, 3, 5]; -const AppPeersManager = { - getPeerPhoto: (peerID: number) => { +export class AppPeersManager { + public getPeerPhoto(peerID: number) { return peerID > 0 ? appUsersManager.getUserPhoto(peerID) : appChatsManager.getChatPhoto(-peerID); - }, + } - getPeerMigratedTo: (peerID: number) => { + public getPeerMigratedTo(peerID: number) { if(peerID >= 0) { return false; } let chat = appChatsManager.getChat(-peerID); if(chat && chat.migrated_to && chat.pFlags.deactivated) { - return AppPeersManager.getPeerID(chat.migrated_to); + return this.getPeerID(chat.migrated_to); } return false; - }, + } - getPeerTitle: (peerID: number | any, plainText = false, onlyFirstName = false) => { + public getPeerTitle(peerID: number | any, plainText = false, onlyFirstName = false) { let peer: any = {}; if(!isObject(peerID)) { - peer = AppPeersManager.getPeer(peerID); + peer = this.getPeer(peerID); } else peer = peerID; let title = ''; @@ -62,9 +62,9 @@ const AppPeersManager = { } return plainText ? title : RichTextProcessor.wrapEmojiText(title); - }, + } - getOutputPeer: (peerID: number) => { + public getOutputPeer(peerID: number): Peer { if(peerID > 0) { return {_: 'peerUser', user_id: peerID}; } @@ -75,29 +75,29 @@ const AppPeersManager = { } return {_: 'peerChat', chat_id: chatID}; - }, + } - getPeerString: (peerID: number) => { + public getPeerString(peerID: number) { if(peerID > 0) { return appUsersManager.getUserString(peerID); } return appChatsManager.getChatString(-peerID); - }, + } - getPeerUsername: (peerID: number): string => { + public getPeerUsername(peerID: number): string { if(peerID > 0) { return appUsersManager.getUser(peerID).username || ''; } return appChatsManager.getChat(-peerID).username || ''; - }, + } - getPeer: (peerID: number) => { + public getPeer(peerID: number) { return peerID > 0 ? appUsersManager.getUser(peerID) : appChatsManager.getChat(-peerID) - }, + } - getPeerID: (peerString: any): number => { + public getPeerID(peerString: any): number { if(typeof(peerString) === 'number') return peerString; else if(isObject(peerString)) { return peerString.user_id @@ -108,29 +108,29 @@ const AppPeersManager = { const peerParams = peerString.substr(1).split('_'); return isUser ? peerParams[0] : -peerParams[0] || 0; - }, + } - isChannel: (peerID: number): boolean => { + public isChannel(peerID: number): boolean { return (peerID < 0) && appChatsManager.isChannel(-peerID); - }, + } - isMegagroup: (peerID: number) => { + public isMegagroup(peerID: number) { return (peerID < 0) && appChatsManager.isMegagroup(-peerID); - }, + } - isAnyGroup: (peerID: number): boolean => { + public isAnyGroup(peerID: number): boolean { return (peerID < 0) && !appChatsManager.isBroadcast(-peerID); - }, + } - isBroadcast: (id: number): boolean => { - return AppPeersManager.isChannel(id) && !AppPeersManager.isMegagroup(id); - }, + public isBroadcast(id: number): boolean { + return this.isChannel(id) && !this.isMegagroup(id); + } - isBot: (peerID: number): boolean => { + public isBot(peerID: number): boolean { return (peerID > 0) && appUsersManager.isBot(peerID); - }, + } - getInputPeer: (peerString: string): any => { + public getInputPeer(peerString: string): InputPeer { var firstChar = peerString.charAt(0); var peerParams = peerString.substr(1).split('_'); let id = +peerParams[0]; @@ -152,7 +152,7 @@ const AppPeersManager = { return { _: 'inputPeerChannel', channel_id: id, - access_hash: peerParams[1] || 0 + access_hash: peerParams[1] || '0' }; } else { return { @@ -160,9 +160,9 @@ const AppPeersManager = { chat_id: id }; } - }, + } - getInputPeerByID: (peerID: number): InputPeer => { + public getInputPeerByID(peerID: number): InputPeer { if(!peerID) { return {_: 'inputPeerEmpty'}; } @@ -181,22 +181,22 @@ const AppPeersManager = { user_id: peerID, access_hash: appUsersManager.getUser(peerID).access_hash }; - }, + } - getInputDialogPeerByID: (peerID: number): InputDialogPeer => { + public getInputDialogPeerByID(peerID: number): InputDialogPeer { return { _: 'inputDialogPeer', - peer: AppPeersManager.getInputPeerByID(peerID) + peer: this.getInputPeerByID(peerID) } - }, + } - getPeerColorByID: (peerID: number, pic = true) => { + public getPeerColorByID(peerID: number, pic = true) { const idx = DialogColorsMap[(peerID < 0 ? -peerID : peerID) % 7]; const color = (pic ? DialogColors : DialogColorsFg)[idx]; return color; - }, + } - getPeerSearchText: (peerID: number) => { + public getPeerSearchText(peerID: number) { let text; if(peerID > 0) { text = '%pu ' + appUsersManager.getUserSearchText(peerID); @@ -206,6 +206,7 @@ const AppPeersManager = { } return text; } -}; +} -export default AppPeersManager; +const appPeersManager = new AppPeersManager(); +export default appPeersManager; diff --git a/src/lib/appManagers/appPhotosManager.ts b/src/lib/appManagers/appPhotosManager.ts index b22769d6..b60ab702 100644 --- a/src/lib/appManagers/appPhotosManager.ts +++ b/src/lib/appManagers/appPhotosManager.ts @@ -228,7 +228,7 @@ export class AppPhotosManager { const photo = this.getPhoto(photoID); // @ts-ignore - if(photo._ == 'photoEmpty') { + if(!photo || photo._ == 'photoEmpty') { throw new Error('preloadPhoto photoEmpty!'); } diff --git a/src/lib/appManagers/appSidebarRight.ts b/src/lib/appManagers/appSidebarRight.ts index fd67f82d..d28e02d7 100644 --- a/src/lib/appManagers/appSidebarRight.ts +++ b/src/lib/appManagers/appSidebarRight.ts @@ -416,7 +416,7 @@ export class AppSidebarRight extends SidebarSlider { return filtered; } - public async performSearchResult(messages: any[], type: string) { + public async performSearchResult(messages: any[], type: SharedMediaType) { const peerID = this.peerID; const elemsToAppend: HTMLElement[] = []; const promises: Promise[] = []; diff --git a/src/lib/utils.ts b/src/lib/utils.ts index 6e4484bc..bf764e16 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,3 +1,4 @@ +import { Dialog } from "./appManagers/appMessagesManager"; /*! * Webogram v0.7.0 - messaging web application for MTProto * https://github.com/zhukov/webogram @@ -156,29 +157,36 @@ type BroadcastEvents = { 'user_update': any, 'user_auth': any, 'peer_changed': any, + 'filter_delete': any, 'filter_update': any, - 'message_edit': any, + 'dialog_draft': any, - 'messages_pending': any, + 'dialog_unread': {peerID: number, count?: number}, + 'dialog_flush': {peerID: number}, + 'dialog_drop': {peerID: number, dialog?: Dialog}, + 'dialog_migrate': any, + 'dialog_top': Dialog, + 'dialog_notify_settings': number, + 'dialogs_multiupdate': {[peerID: string]: Dialog}, + 'dialogs_archived_unread': any, + 'history_append': any, 'history_update': any, - 'dialogs_multiupdate': any, - 'dialog_unread': any, - 'dialog_flush': any, - 'dialog_drop': any, - 'dialog_migrate': any, - 'dialog_top': any, 'history_reply_markup': any, 'history_multiappend': any, - 'messages_read': any, - 'history_delete': any, - 'history_forbidden': any, - 'history_reload': any, + 'history_delete': {peerID: number, msgs: {[mid: number]: true}}, + 'history_forbidden': number, + 'history_reload': number, + 'history_request': any, + + 'message_edit': any, 'message_views': any, 'message_sent': any, - 'history_request': any, + 'messages_pending': void, + 'messages_read': any, 'messages_downloaded': any, + 'contacts_update': any, 'avatar_update': any, 'stickers_installed': any, @@ -186,7 +194,6 @@ type BroadcastEvents = { 'chat_full_update': any, 'peer_pinned_message': any, 'poll_update': any, - 'dialogs_archived_unread': any, 'audio_play': any, 'audio_pause': any, 'chat_update': any, @@ -195,7 +202,6 @@ type BroadcastEvents = { 'channel_settings': any, 'webpage_updated': any, 'draft_updated': any, - 'dialog_notify_settings': number, }; export const $rootScope = { diff --git a/src/scripts/in/schema_additional_params.json b/src/scripts/in/schema_additional_params.json index 6a305b38..18314a49 100644 --- a/src/scripts/in/schema_additional_params.json +++ b/src/scripts/in/schema_additional_params.json @@ -41,4 +41,17 @@ "params": [ {"name": "url", "type": "string"} ] +}, { + "predicate": "dialog", + "params": [ + {"name": "index", "type": "number"}, + {"name": "peerID", "type": "number"} + ] +}, { + "predicate": "dialogFolder", + "params": [ + {"name": "index", "type": "number"}, + {"name": "peerID", "type": "number"}, + {"name": "folder_id", "type": "number"} + ] }] \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 74792780..95367ae9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,7 +21,7 @@ // "noEmit": true, /* Do not emit outputs. */ //"importHelpers": true, /* Import emit helpers from 'tslib'. */ "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ - // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ /* Strict Type-Checking Options */ "strict": true, /* Enable all strict type-checking options. */ @@ -30,6 +30,7 @@ // "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ "strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */ + // "skipLibCheck": true, // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ @@ -40,7 +41,7 @@ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ /* Module Resolution Options */ - "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ @@ -66,6 +67,7 @@ "./public/recorder.min.js", "public", "coverage", + "./src/scripts", "./public/*.js", "./src/lib/crypto/crypto.worker.js", "./src/vendor/StackBlur.js",