Browse Source

Fix displaying chat from the archive in main list

master
morethanwords 4 years ago
parent
commit
8992a6e7af
  1. 80
      src/components/chat/audio.ts
  2. 167
      src/components/chat/contextMenu.ts
  3. 32
      src/components/chat/input.ts
  4. 187
      src/components/chat/search.ts
  5. 3
      src/global.d.ts
  6. 9
      src/layer.d.ts
  7. 4
      src/lib/appManagers/appDialogsManager.ts
  8. 438
      src/lib/appManagers/appImManager.ts
  9. 141
      src/lib/appManagers/appMessagesManager.ts
  10. 89
      src/lib/appManagers/appPeersManager.ts
  11. 2
      src/lib/appManagers/appPhotosManager.ts
  12. 2
      src/lib/appManagers/appSidebarRight.ts
  13. 36
      src/lib/utils.ts
  14. 13
      src/scripts/in/schema_additional_params.json
  15. 4
      tsconfig.json

80
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');
});
}
}

167
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);
});
}
}

32
src/components/chatInput.ts → src/components/chat/input.ts

@ -1,19 +1,19 @@
import Scrollable from "./scrollable_new"; import Scrollable from "../scrollable_new";
import { RichTextProcessor } from "../lib/richtextprocessor"; import { RichTextProcessor } from "../../lib/richtextprocessor";
import apiManager from "../lib/mtproto/mtprotoworker"; import apiManager from "../../lib/mtproto/mtprotoworker";
import appWebPagesManager from "../lib/appManagers/appWebPagesManager"; import appWebPagesManager from "../../lib/appManagers/appWebPagesManager";
import appImManager from "../lib/appManagers/appImManager"; import appImManager from "../../lib/appManagers/appImManager";
import { getRichValue, calcImageInBox, cancelEvent } from "../lib/utils"; import { getRichValue, calcImageInBox, cancelEvent } from "../../lib/utils";
import { wrapDocument, wrapReply } from "./wrappers"; import { wrapDocument, wrapReply } from "../wrappers";
import appMessagesManager from "../lib/appManagers/appMessagesManager"; import appMessagesManager from "../../lib/appManagers/appMessagesManager";
import { Layouter, RectPart } from "./groupedLayout"; import { Layouter, RectPart } from "../groupedLayout";
import Recorder from '../../public/recorder.min'; import Recorder from '../../../public/recorder.min';
//import Recorder from '../opus-recorder/dist/recorder.min'; //import Recorder from '../opus-recorder/dist/recorder.min';
import opusDecodeController from "../lib/opusDecodeController"; import opusDecodeController from "../../lib/opusDecodeController";
import { touchSupport } from "../lib/config"; import { touchSupport } from "../../lib/config";
import appDocsManager from "../lib/appManagers/appDocsManager"; import appDocsManager from "../../lib/appManagers/appDocsManager";
import emoticonsDropdown from "./emoticonsDropdown"; import emoticonsDropdown from "../emoticonsDropdown";
import PopupCreatePoll from "./popupCreatePoll"; import PopupCreatePoll from "../popupCreatePoll";
export class ChatInput { export class ChatInput {
public pageEl = document.getElementById('page-chats') as HTMLDivElement; public pageEl = document.getElementById('page-chats') as HTMLDivElement;
@ -729,7 +729,7 @@ export class ChatInput {
public sendMessageWithDocument(document: any) { public sendMessageWithDocument(document: any) {
document = appDocsManager.getDoc(document); document = appDocsManager.getDoc(document);
if(document._ != 'documentEmpty') { if(document && document._ != 'documentEmpty') {
appMessagesManager.sendFile(appImManager.peerID, document, {isMedia: true, replyToMsgID: this.replyToMsgID}); appMessagesManager.sendFile(appImManager.peerID, document, {isMedia: true, replyToMsgID: this.replyToMsgID});
this.onMessageSent(false, true); this.onMessageSent(false, true);

187
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<any>;
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);
};
}

3
src/global.d.ts vendored

@ -5,6 +5,3 @@ declare module 'worker-loader!*' {
export default WebpackWorker; export default WebpackWorker;
} }
declare function setInterval(callback: (...args: any[]) => void, ms: number): number;
declare function setTimeout(callback: (...args: any[]) => void, ms: number): number;

9
src/layer.d.ts vendored

@ -1076,7 +1076,9 @@ export namespace Dialog {
notify_settings: PeerNotifySettings, notify_settings: PeerNotifySettings,
pts?: number, pts?: number,
draft?: DraftMessage, draft?: DraftMessage,
folder_id?: number folder_id?: number,
index?: number,
peerID?: number
}; };
export type dialogFolder = { export type dialogFolder = {
@ -1091,7 +1093,10 @@ export namespace Dialog {
unread_muted_peers_count: number, unread_muted_peers_count: number,
unread_unmuted_peers_count: number, unread_unmuted_peers_count: number,
unread_muted_messages_count: number, unread_muted_messages_count: number,
unread_unmuted_messages_count: number unread_unmuted_messages_count: number,
index?: number,
peerID?: number,
folder_id?: number
}; };
} }

4
src/lib/appManagers/appDialogsManager.ts

@ -733,7 +733,7 @@ export class AppDialogsManager {
this.setListClickListener(ul, null, true); this.setListClickListener(ul, null, true);
if(!this.showFiltersTimeout) { if(!this.showFiltersTimeout) {
this.showFiltersTimeout = setTimeout(() => { this.showFiltersTimeout = window.setTimeout(() => {
this.showFiltersTimeout = 0; this.showFiltersTimeout = 0;
this.folders.menuScrollContainer.classList.remove('hide'); this.folders.menuScrollContainer.classList.remove('hide');
this.setFiltersUnreadCount(); this.setFiltersUnreadCount();
@ -1086,7 +1086,7 @@ export class AppDialogsManager {
public accumulateArchivedUnread() { public accumulateArchivedUnread() {
if(this.accumulateArchivedTimeout) return; if(this.accumulateArchivedTimeout) return;
this.accumulateArchivedTimeout = setTimeout(() => { this.accumulateArchivedTimeout = window.setTimeout(() => {
this.accumulateArchivedTimeout = 0; this.accumulateArchivedTimeout = 0;
const dialogs = appMessagesManager.dialogsStorage.getFolder(1); const dialogs = appMessagesManager.dialogsStorage.getFolder(1);
const sum = dialogs.reduce((acc, dialog) => acc + dialog.unread_count, 0); const sum = dialogs.reduce((acc, dialog) => acc + dialog.unread_count, 0);

438
src/lib/appManagers/appImManager.ts

@ -14,10 +14,10 @@ import { logger, LogLevels } from "../logger";
import appMediaViewer from "./appMediaViewer"; import appMediaViewer from "./appMediaViewer";
import appSidebarLeft from "./appSidebarLeft"; import appSidebarLeft from "./appSidebarLeft";
import appChatsManager, { Channel, Chat } from "./appChatsManager"; 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 ProgressivePreloader from '../../components/preloader';
import { openBtnMenu, formatPhoneNumber, positionMenu, parseMenuButtonsTo, attachContextMenuListener } from '../../components/misc'; import { formatPhoneNumber, parseMenuButtonsTo } from '../../components/misc';
import { ChatInput } from '../../components/chatInput'; import { ChatInput } from '../../components/chat/input';
//import Scrollable from '../../components/scrollable'; //import Scrollable from '../../components/scrollable';
import Scrollable from '../../components/scrollable_new'; import Scrollable from '../../components/scrollable_new';
import BubbleGroups from '../../components/bubbleGroups'; import BubbleGroups from '../../components/bubbleGroups';
@ -28,19 +28,18 @@ import appStickersManager from './appStickersManager';
import AvatarElement from '../../components/avatar'; import AvatarElement from '../../components/avatar';
import appInlineBotsManager from './AppInlineBotsManager'; import appInlineBotsManager from './AppInlineBotsManager';
import StickyIntersector from '../../components/stickyIntersector'; import StickyIntersector from '../../components/stickyIntersector';
import { PopupButton, PopupPeer } from '../../components/popup';
import { mediaSizes, touchSupport, isAndroid, isApple } from '../config'; import { mediaSizes, touchSupport, isAndroid, isApple } from '../config';
import animationIntersector from '../../components/animationIntersector'; import animationIntersector from '../../components/animationIntersector';
import PopupStickers from '../../components/popupStickers'; import PopupStickers from '../../components/popupStickers';
import SearchInput from '../../components/searchInput';
import AppSearch, { SearchGroup } from '../../components/appSearch';
import PopupDatePicker from '../../components/popupDatepicker'; import PopupDatePicker from '../../components/popupDatepicker';
import appMediaPlaybackController from '../../components/appMediaPlaybackController';
import appPollsManager from './appPollsManager'; import appPollsManager from './appPollsManager';
import { ripple } from '../../components/ripple'; import { ripple } from '../../components/ripple';
import { horizontalMenu } from '../../components/horizontalMenu'; import { horizontalMenu } from '../../components/horizontalMenu';
import AudioElement from '../../components/audio'; import AudioElement from '../../components/audio';
import { InputNotifyPeer, InputPeerNotifySettings } from '../../layer'; 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!'); //console.log('appImManager included33!');
@ -50,419 +49,6 @@ const testScroll = false;
const ANIMATIONGROUP = 'chat'; 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<any>;
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 { export class AppImManager {
public columnEl = document.getElementById('column-center') as HTMLDivElement; public columnEl = document.getElementById('column-center') as HTMLDivElement;
public btnJoin = this.columnEl.querySelector('.chat-join') as HTMLButtonElement; public btnJoin = this.columnEl.querySelector('.chat-join') as HTMLButtonElement;
@ -651,14 +237,14 @@ export class AppImManager {
if(message.media) { if(message.media) {
if(message.media.photo) { if(message.media.photo) {
const photo = appPhotosManager.getPhoto(tempID); const photo = appPhotosManager.getPhoto(tempID);
//if(photo._ != 'photoEmpty') { if(/* photo._ != 'photoEmpty' */photo) {
const newPhoto = message.media.photo; const newPhoto = message.media.photo;
newPhoto.downloaded = photo.downloaded; newPhoto.downloaded = photo.downloaded;
newPhoto.url = photo.url; newPhoto.url = photo.url;
//} }
} else if(message.media.document) { } else if(message.media.document) {
const doc = appDocsManager.getDoc(tempID); 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; const newDoc = message.media.document;
newDoc.downloaded = doc.downloaded; newDoc.downloaded = doc.downloaded;
newDoc.url = doc.url; newDoc.url = doc.url;
@ -1223,7 +809,7 @@ export class AppImManager {
this.chatInner.classList.add('is-scrolling'); this.chatInner.classList.add('is-scrolling');
} }
this.isScrollingTimeout = setTimeout(() => { this.isScrollingTimeout = window.setTimeout(() => {
this.chatInner.classList.remove('is-scrolling'); this.chatInner.classList.remove('is-scrolling');
this.isScrollingTimeout = 0; this.isScrollingTimeout = 0;
}, 1350); }, 1350);
@ -1283,7 +869,7 @@ export class AppImManager {
clearTimeout(this.isScrollingTimeout); clearTimeout(this.isScrollingTimeout);
} }
this.isScrollingTimeout = setTimeout(() => { this.isScrollingTimeout = window.setTimeout(() => {
this.chatInner.classList.remove('is-scrolling'); this.chatInner.classList.remove('is-scrolling');
this.isScrollingTimeout = 0; this.isScrollingTimeout = 0;
}, 1350); }, 1350);
@ -3035,7 +2621,7 @@ export class AppImManager {
} }
} }
this.typingTimeouts[peerID] = setTimeout(() => { this.typingTimeouts[peerID] = window.setTimeout(() => {
this.typingTimeouts[peerID] = 0; this.typingTimeouts[peerID] = 0;
delete this.typingUsers[update.user_id]; delete this.typingUsers[update.user_id];

141
src/lib/appManagers/appMessagesManager.ts

@ -24,9 +24,10 @@ import { Modify } from "../../types";
import { logger, LogLevels } from "../logger"; import { logger, LogLevels } from "../logger";
import type {ApiFileManager} from '../mtproto/apiFileManager'; import type {ApiFileManager} from '../mtproto/apiFileManager';
import appDownloadManager from "./appDownloadManager"; 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'); //console.trace('include');
// TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет
const APITIMEOUT = 0; const APITIMEOUT = 0;
@ -47,27 +48,7 @@ export type HistoryResult = {
unreadSkip: boolean unreadSkip: boolean
}; };
export type Dialog = { export type Dialog = MTDialog.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 class DialogsStorage { export class DialogsStorage {
public dialogs: {[peerID: string]: Dialog} = {}; public dialogs: {[peerID: string]: Dialog} = {};
@ -487,7 +468,7 @@ export class AppMessagesManager {
public pendingByRandomID: {[randomID: string]: [number, number]} = {}; public pendingByRandomID: {[randomID: string]: [number, number]} = {};
public pendingByMessageID: any = {}; public pendingByMessageID: any = {};
public pendingAfterMsgs: any = {}; public pendingAfterMsgs: any = {};
public pendingTopMsgs: any = {}; public pendingTopMsgs: {[peerID: string]: number} = {};
public sendFilePromise: CancellablePromise<void> = Promise.resolve(); public sendFilePromise: CancellablePromise<void> = Promise.resolve();
public tempID = -1; public tempID = -1;
public tempFinalizeCallbacks: any = {}; public tempFinalizeCallbacks: any = {};
@ -1009,7 +990,7 @@ export class AppMessagesManager {
attributes.push({_: 'documentAttributeFilename', file_name: fileName || apiFileName}); attributes.push({_: 'documentAttributeFilename', file_name: fileName || apiFileName});
if(['document', 'video', 'audio', 'voice'].indexOf(attachType) !== -1 && !isDocument) { if(['document', 'video', 'audio', 'voice'].indexOf(attachType) !== -1 && !isDocument) {
let doc: any = { let doc: MyDocument = {
_: 'document', _: 'document',
id: '' + messageID, id: '' + messageID,
duration: options.duration, duration: options.duration,
@ -1021,7 +1002,7 @@ export class AppMessagesManager {
mime_type: fileType, mime_type: fileType,
url: options.objectURL || '', url: options.objectURL || '',
size: file.size size: file.size
}; } as any;
appDocsManager.saveDoc(doc); appDocsManager.saveDoc(doc);
} }
@ -1297,21 +1278,18 @@ export class AppMessagesManager {
if(file.type.indexOf('video/') === 0) { if(file.type.indexOf('video/') === 0) {
let flags = 1; let flags = 1;
let videoAttribute = { let videoAttribute: DocumentAttribute.documentAttributeVideo = {
_: 'documentAttributeVideo', _: 'documentAttributeVideo',
flags: flags, flags: flags,
pFlags: { // that's only for client, not going to telegram pFlags: { // that's only for client, not going to telegram
supports_streaming: true, supports_streaming: true
round_message: false
}, },
round_message: false,
supports_streaming: true,
duration: details.duration, duration: details.duration,
w: details.width, w: details.width,
h: details.height h: details.height
}; };
let doc: any = { let doc: MyDocument = {
_: 'document', _: 'document',
id: '' + messageID, id: '' + messageID,
attributes: [videoAttribute], attributes: [videoAttribute],
@ -1320,7 +1298,7 @@ export class AppMessagesManager {
mime_type: file.type, mime_type: file.type,
url: details.objectURL || '', url: details.objectURL || '',
size: file.size size: file.size
}; } as any;
appDocsManager.saveDoc(doc); appDocsManager.saveDoc(doc);
media.document = doc; media.document = doc;
@ -1894,9 +1872,11 @@ export class AppMessagesManager {
hash: 0 hash: 0
}, { }, {
timeout: APITIMEOUT timeout: APITIMEOUT
}).then((dialogsResult: any) => { }).then((dialogsResult) => {
///////this.log('messages.getDialogs result:', dialogsResult); ///////this.log('messages.getDialogs result:', dialogsResult);
if(dialogsResult._ == 'messages.dialogsNotModified') return null;
if(!offsetDate) { if(!offsetDate) {
telegramMeWebService.setAuthorized(true); telegramMeWebService.setAuthorized(true);
} }
@ -1905,21 +1885,31 @@ export class AppMessagesManager {
appChatsManager.saveApiChats(dialogsResult.chats); appChatsManager.saveApiChats(dialogsResult.chats);
this.saveMessages(dialogsResult.messages); this.saveMessages(dialogsResult.messages);
var maxSeenIdIncremented = offsetDate ? true : false; let maxSeenIdIncremented = offsetDate ? true : false;
var hasPrepend = false; let hasPrepend = false;
let length = dialogsResult.dialogs.length; let noIDsDialogs: {[peerID: number]: Dialog} = {};
let noIDsDialogs: any = {}; (dialogsResult.dialogs as Dialog[]).forEachReverse(dialog => {
for(let i = length - 1; i >= 0; --i) { //const d = Object.assign({}, dialog);
let dialog = dialogsResult.dialogs[i]; // ! нужно передавать 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) { if(offsetIndex && dialog.index > offsetIndex) {
this.newDialogsToHandle[dialog.peerID] = dialog; this.newDialogsToHandle[dialog.peerID] = dialog;
hasPrepend = true; hasPrepend = true;
} }
// ! это может случиться, если запрос идёт не по папке 0, а по 1. почему-то read'ов нет
// ! в итоге, чтобы получить 1 диалог, делается первый запрос по папке 0, потом запрос для архивных по папке 1, и потом ещё перезагрузка архивного диалога
if(!dialog.read_inbox_max_id && !dialog.read_outbox_max_id) { if(!dialog.read_inbox_max_id && !dialog.read_outbox_max_id) {
noIDsDialogs[dialog.peerID] = dialog; noIDsDialogs[dialog.peerID] = dialog;
/* if(dialog.peerID == 239602833) {
this.log.error('lun bot', folderID);
} */
} }
if(!maxSeenIdIncremented && if(!maxSeenIdIncremented &&
@ -1927,7 +1917,7 @@ export class AppMessagesManager {
this.incrementMaxSeenID(dialog.top_message); this.incrementMaxSeenID(dialog.top_message);
maxSeenIdIncremented = true; maxSeenIdIncremented = true;
} }
} });
if(Object.keys(noIDsDialogs).length) { if(Object.keys(noIDsDialogs).length) {
//setTimeout(() => { // test bad situation //setTimeout(() => { // test bad situation
@ -1941,9 +1931,11 @@ export class AppMessagesManager {
//}, 10e3); //}, 10e3);
} }
const count = (dialogsResult as MessagesDialogs.messagesDialogsSlice).count;
if(!dialogsResult.dialogs.length || if(!dialogsResult.dialogs.length ||
!dialogsResult.count || !count ||
dialogs.length >= dialogsResult.count) { dialogs.length >= count) {
this.dialogsStorage.allDialogsLoaded[folderID] = true; this.dialogsStorage.allDialogsLoaded[folderID] = true;
} }
@ -1953,7 +1945,7 @@ export class AppMessagesManager {
$rootScope.$broadcast('dialogs_multiupdate', {}); $rootScope.$broadcast('dialogs_multiupdate', {});
} }
return dialogsResult.count; return count;
}); });
} }
@ -2115,11 +2107,11 @@ export class AppMessagesManager {
} }
if(justClear) { if(justClear) {
$rootScope.$broadcast('dialog_flush', {peerID: peerID}); $rootScope.$broadcast('dialog_flush', {peerID});
} else { } else {
this.dialogsStorage.dropDialog(peerID); this.dialogsStorage.dropDialog(peerID);
$rootScope.$broadcast('dialog_drop', {peerID: peerID}); $rootScope.$broadcast('dialog_drop', {peerID});
} }
}); });
} }
@ -2598,7 +2590,9 @@ export class AppMessagesManager {
return true; return true;
} }
public applyConversations(dialogsResult: any) { public applyConversations(dialogsResult: MessagesPeerDialogs.messagesPeerDialogs) {
// * В эту функцию попадут только те диалоги, в которых есть read_inbox_max_id и read_outbox_max_id, в отличие от тех, что будут в getTopMessages
appUsersManager.saveApiUsers(dialogsResult.users); appUsersManager.saveApiUsers(dialogsResult.users);
appChatsManager.saveApiChats(dialogsResult.chats); appChatsManager.saveApiChats(dialogsResult.chats);
this.saveMessages(dialogsResult.messages); this.saveMessages(dialogsResult.messages);
@ -2607,24 +2601,30 @@ export class AppMessagesManager {
const updatedDialogs: {[peerID: number]: Dialog} = {}; const updatedDialogs: {[peerID: number]: Dialog} = {};
let hasUpdated = false; let hasUpdated = false;
dialogsResult.dialogs.forEach((dialog: any) => { (dialogsResult.dialogs as Dialog[]).forEach((dialog) => {
const peerID = appPeersManager.getPeerID(dialog.peer); const peerID = appPeersManager.getPeerID(dialog.peer);
let topMessage = dialog.top_message; let topMessage = dialog.top_message;
const topPendingMesage = this.pendingTopMsgs[peerID]; const topPendingMessage = this.pendingTopMsgs[peerID];
if(topPendingMesage) { if(topPendingMessage) {
if(!topMessage || this.getMessage(topPendingMesage).date > this.getMessage(topMessage).date) { if(!topMessage || this.getMessage(topPendingMessage).date > this.getMessage(topMessage).date) {
dialog.top_message = topMessage = topPendingMesage; dialog.top_message = topMessage = topPendingMessage;
} }
} }
/* const d = Object.assign({}, dialog);
if(peerID == 239602833) {
this.log.error('applyConversation lun', dialog, d);
} */
if(topMessage) { if(topMessage) {
const wasDialogBefore = this.getDialogByPeerID(peerID)[0]; const wasDialogBefore = this.getDialogByPeerID(peerID)[0];
// here need to just replace, not FULL replace dialog! WARNING // 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 = {}; if(!dialog.pFlags) dialog.pFlags = {};
dialog.pFlags.pinned = true; dialog.pFlags.pinned = true;
} } */
this.saveConversation(dialog); this.saveConversation(dialog);
@ -2656,11 +2656,16 @@ export class AppMessagesManager {
} }
} }
public saveConversation(dialog: Dialog) { public saveConversation(dialog: Dialog, folderID = 0) {
const peerID = appPeersManager.getPeerID(dialog.peer); const peerID = appPeersManager.getPeerID(dialog.peer);
if(!peerID) { if(!peerID) {
return false; 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 channelID = appPeersManager.isChannel(peerID) ? -peerID : 0;
const peerText = appPeersManager.getPeerSearchText(peerID); const peerText = appPeersManager.getPeerSearchText(peerID);
searchIndexManager.indexObject(peerID, peerText, this.dialogsIndex); 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_inbox_max_id = appMessagesIDsManager.getFullMessageID(dialog.read_inbox_max_id, channelID);
dialog.read_outbox_max_id = appMessagesIDsManager.getFullMessageID(dialog.read_outbox_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; dialog.peerID = peerID;
this.dialogsStorage.generateIndexForDialog(dialog); this.dialogsStorage.generateIndexForDialog(dialog);
@ -3057,7 +3069,7 @@ export class AppMessagesManager {
this.incrementMaxSeenID(newMaxSeenID); this.incrementMaxSeenID(newMaxSeenID);
} }
$rootScope.$broadcast('dialogs_multiupdate', this.newDialogsToHandle); $rootScope.$broadcast('dialogs_multiupdate', this.newDialogsToHandle as any);
this.newDialogsToHandle = {}; this.newDialogsToHandle = {};
}; };
@ -3502,7 +3514,7 @@ export class AppMessagesManager {
if(!update.order) { if(!update.order) {
apiManager.invokeApi('messages.getPinnedDialogs', { apiManager.invokeApi('messages.getPinnedDialogs', {
folder_id: folderID folder_id: folderID
}).then((dialogsResult: any) => { }).then((dialogsResult) => {
dialogsResult.dialogs.reverse(); dialogsResult.dialogs.reverse();
this.applyConversations(dialogsResult); this.applyConversations(dialogsResult);
@ -3750,8 +3762,9 @@ export class AppMessagesManager {
} }
} }
Object.keys(historiesUpdated).forEach(peerID => { Object.keys(historiesUpdated).forEach(_peerID => {
let updatedData = historiesUpdated[+peerID]; const peerID = +_peerID;
let updatedData = historiesUpdated[peerID];
let historyStorage = this.historiesStorage[peerID]; let historyStorage = this.historiesStorage[peerID];
if(historyStorage !== undefined) { if(historyStorage !== undefined) {
let newHistory: number[] = []; let newHistory: number[] = [];
@ -3778,22 +3791,22 @@ export class AppMessagesManager {
} }
historyStorage.pending = newPending; 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(foundDialog) {
if(updatedData.unread) { if(updatedData.unread) {
foundDialog.unread_count -= updatedData.unread; foundDialog.unread_count -= updatedData.unread;
$rootScope.$broadcast('dialog_unread', { $rootScope.$broadcast('dialog_unread', {
peerID: peerID, peerID,
count: foundDialog.unread_count count: foundDialog.unread_count
}); });
} }
if(updatedData.msgs[foundDialog.top_message]) { if(updatedData.msgs[foundDialog.top_message]) {
this.reloadConversation(+peerID); this.reloadConversation(peerID);
} }
} }
}); });

89
src/lib/appManagers/appPeersManager.ts

@ -2,7 +2,7 @@ import appUsersManager from "./appUsersManager";
import appChatsManager from "./appChatsManager"; import appChatsManager from "./appChatsManager";
import { isObject } from "../utils"; import { isObject } from "../utils";
import { RichTextProcessor } from "../richtextprocessor"; 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 // 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 DialogColors = ['#e17076', '#7bc862', '#e5ca77', '#65AADD', '#a695e7', '#ee7aae', '#6ec9cb', '#faa774'];
const DialogColorsMap = [0, 7, 4, 1, 6, 3, 5]; const DialogColorsMap = [0, 7, 4, 1, 6, 3, 5];
const AppPeersManager = { export class AppPeersManager {
getPeerPhoto: (peerID: number) => { public getPeerPhoto(peerID: number) {
return peerID > 0 return peerID > 0
? appUsersManager.getUserPhoto(peerID) ? appUsersManager.getUserPhoto(peerID)
: appChatsManager.getChatPhoto(-peerID); : appChatsManager.getChatPhoto(-peerID);
}, }
getPeerMigratedTo: (peerID: number) => { public getPeerMigratedTo(peerID: number) {
if(peerID >= 0) { if(peerID >= 0) {
return false; return false;
} }
let chat = appChatsManager.getChat(-peerID); let chat = appChatsManager.getChat(-peerID);
if(chat && chat.migrated_to && chat.pFlags.deactivated) { if(chat && chat.migrated_to && chat.pFlags.deactivated) {
return AppPeersManager.getPeerID(chat.migrated_to); return this.getPeerID(chat.migrated_to);
} }
return false; return false;
}, }
getPeerTitle: (peerID: number | any, plainText = false, onlyFirstName = false) => { public getPeerTitle(peerID: number | any, plainText = false, onlyFirstName = false) {
let peer: any = {}; let peer: any = {};
if(!isObject(peerID)) { if(!isObject(peerID)) {
peer = AppPeersManager.getPeer(peerID); peer = this.getPeer(peerID);
} else peer = peerID; } else peer = peerID;
let title = ''; let title = '';
@ -62,9 +62,9 @@ const AppPeersManager = {
} }
return plainText ? title : RichTextProcessor.wrapEmojiText(title); return plainText ? title : RichTextProcessor.wrapEmojiText(title);
}, }
getOutputPeer: (peerID: number) => { public getOutputPeer(peerID: number): Peer {
if(peerID > 0) { if(peerID > 0) {
return {_: 'peerUser', user_id: peerID}; return {_: 'peerUser', user_id: peerID};
} }
@ -75,29 +75,29 @@ const AppPeersManager = {
} }
return {_: 'peerChat', chat_id: chatID}; return {_: 'peerChat', chat_id: chatID};
}, }
getPeerString: (peerID: number) => { public getPeerString(peerID: number) {
if(peerID > 0) { if(peerID > 0) {
return appUsersManager.getUserString(peerID); return appUsersManager.getUserString(peerID);
} }
return appChatsManager.getChatString(-peerID); return appChatsManager.getChatString(-peerID);
}, }
getPeerUsername: (peerID: number): string => { public getPeerUsername(peerID: number): string {
if(peerID > 0) { if(peerID > 0) {
return appUsersManager.getUser(peerID).username || ''; return appUsersManager.getUser(peerID).username || '';
} }
return appChatsManager.getChat(-peerID).username || ''; return appChatsManager.getChat(-peerID).username || '';
}, }
getPeer: (peerID: number) => { public getPeer(peerID: number) {
return peerID > 0 return peerID > 0
? appUsersManager.getUser(peerID) ? appUsersManager.getUser(peerID)
: appChatsManager.getChat(-peerID) : appChatsManager.getChat(-peerID)
}, }
getPeerID: (peerString: any): number => { public getPeerID(peerString: any): number {
if(typeof(peerString) === 'number') return peerString; if(typeof(peerString) === 'number') return peerString;
else if(isObject(peerString)) { else if(isObject(peerString)) {
return peerString.user_id return peerString.user_id
@ -108,29 +108,29 @@ const AppPeersManager = {
const peerParams = peerString.substr(1).split('_'); const peerParams = peerString.substr(1).split('_');
return isUser ? peerParams[0] : -peerParams[0] || 0; return isUser ? peerParams[0] : -peerParams[0] || 0;
}, }
isChannel: (peerID: number): boolean => { public isChannel(peerID: number): boolean {
return (peerID < 0) && appChatsManager.isChannel(-peerID); return (peerID < 0) && appChatsManager.isChannel(-peerID);
}, }
isMegagroup: (peerID: number) => { public isMegagroup(peerID: number) {
return (peerID < 0) && appChatsManager.isMegagroup(-peerID); return (peerID < 0) && appChatsManager.isMegagroup(-peerID);
}, }
isAnyGroup: (peerID: number): boolean => { public isAnyGroup(peerID: number): boolean {
return (peerID < 0) && !appChatsManager.isBroadcast(-peerID); return (peerID < 0) && !appChatsManager.isBroadcast(-peerID);
}, }
isBroadcast: (id: number): boolean => { public isBroadcast(id: number): boolean {
return AppPeersManager.isChannel(id) && !AppPeersManager.isMegagroup(id); return this.isChannel(id) && !this.isMegagroup(id);
}, }
isBot: (peerID: number): boolean => { public isBot(peerID: number): boolean {
return (peerID > 0) && appUsersManager.isBot(peerID); return (peerID > 0) && appUsersManager.isBot(peerID);
}, }
getInputPeer: (peerString: string): any => { public getInputPeer(peerString: string): InputPeer {
var firstChar = peerString.charAt(0); var firstChar = peerString.charAt(0);
var peerParams = peerString.substr(1).split('_'); var peerParams = peerString.substr(1).split('_');
let id = +peerParams[0]; let id = +peerParams[0];
@ -152,7 +152,7 @@ const AppPeersManager = {
return { return {
_: 'inputPeerChannel', _: 'inputPeerChannel',
channel_id: id, channel_id: id,
access_hash: peerParams[1] || 0 access_hash: peerParams[1] || '0'
}; };
} else { } else {
return { return {
@ -160,9 +160,9 @@ const AppPeersManager = {
chat_id: id chat_id: id
}; };
} }
}, }
getInputPeerByID: (peerID: number): InputPeer => { public getInputPeerByID(peerID: number): InputPeer {
if(!peerID) { if(!peerID) {
return {_: 'inputPeerEmpty'}; return {_: 'inputPeerEmpty'};
} }
@ -181,22 +181,22 @@ const AppPeersManager = {
user_id: peerID, user_id: peerID,
access_hash: appUsersManager.getUser(peerID).access_hash access_hash: appUsersManager.getUser(peerID).access_hash
}; };
}, }
getInputDialogPeerByID: (peerID: number): InputDialogPeer => { public getInputDialogPeerByID(peerID: number): InputDialogPeer {
return { return {
_: 'inputDialogPeer', _: '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 idx = DialogColorsMap[(peerID < 0 ? -peerID : peerID) % 7];
const color = (pic ? DialogColors : DialogColorsFg)[idx]; const color = (pic ? DialogColors : DialogColorsFg)[idx];
return color; return color;
}, }
getPeerSearchText: (peerID: number) => { public getPeerSearchText(peerID: number) {
let text; let text;
if(peerID > 0) { if(peerID > 0) {
text = '%pu ' + appUsersManager.getUserSearchText(peerID); text = '%pu ' + appUsersManager.getUserSearchText(peerID);
@ -206,6 +206,7 @@ const AppPeersManager = {
} }
return text; return text;
} }
}; }
export default AppPeersManager; const appPeersManager = new AppPeersManager();
export default appPeersManager;

2
src/lib/appManagers/appPhotosManager.ts

@ -228,7 +228,7 @@ export class AppPhotosManager {
const photo = this.getPhoto(photoID); const photo = this.getPhoto(photoID);
// @ts-ignore // @ts-ignore
if(photo._ == 'photoEmpty') { if(!photo || photo._ == 'photoEmpty') {
throw new Error('preloadPhoto photoEmpty!'); throw new Error('preloadPhoto photoEmpty!');
} }

2
src/lib/appManagers/appSidebarRight.ts

@ -416,7 +416,7 @@ export class AppSidebarRight extends SidebarSlider {
return filtered; return filtered;
} }
public async performSearchResult(messages: any[], type: string) { public async performSearchResult(messages: any[], type: SharedMediaType) {
const peerID = this.peerID; const peerID = this.peerID;
const elemsToAppend: HTMLElement[] = []; const elemsToAppend: HTMLElement[] = [];
const promises: Promise<any>[] = []; const promises: Promise<any>[] = [];

36
src/lib/utils.ts

@ -1,3 +1,4 @@
import { Dialog } from "./appManagers/appMessagesManager";
/*! /*!
* Webogram v0.7.0 - messaging web application for MTProto * Webogram v0.7.0 - messaging web application for MTProto
* https://github.com/zhukov/webogram * https://github.com/zhukov/webogram
@ -156,29 +157,36 @@ type BroadcastEvents = {
'user_update': any, 'user_update': any,
'user_auth': any, 'user_auth': any,
'peer_changed': any, 'peer_changed': any,
'filter_delete': any, 'filter_delete': any,
'filter_update': any, 'filter_update': any,
'message_edit': any,
'dialog_draft': 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_append': any,
'history_update': 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_reply_markup': any,
'history_multiappend': any, 'history_multiappend': any,
'messages_read': any, 'history_delete': {peerID: number, msgs: {[mid: number]: true}},
'history_delete': any, 'history_forbidden': number,
'history_forbidden': any, 'history_reload': number,
'history_reload': any, 'history_request': any,
'message_edit': any,
'message_views': any, 'message_views': any,
'message_sent': any, 'message_sent': any,
'history_request': any, 'messages_pending': void,
'messages_read': any,
'messages_downloaded': any, 'messages_downloaded': any,
'contacts_update': any, 'contacts_update': any,
'avatar_update': any, 'avatar_update': any,
'stickers_installed': any, 'stickers_installed': any,
@ -186,7 +194,6 @@ type BroadcastEvents = {
'chat_full_update': any, 'chat_full_update': any,
'peer_pinned_message': any, 'peer_pinned_message': any,
'poll_update': any, 'poll_update': any,
'dialogs_archived_unread': any,
'audio_play': any, 'audio_play': any,
'audio_pause': any, 'audio_pause': any,
'chat_update': any, 'chat_update': any,
@ -195,7 +202,6 @@ type BroadcastEvents = {
'channel_settings': any, 'channel_settings': any,
'webpage_updated': any, 'webpage_updated': any,
'draft_updated': any, 'draft_updated': any,
'dialog_notify_settings': number,
}; };
export const $rootScope = { export const $rootScope = {

13
src/scripts/in/schema_additional_params.json

@ -41,4 +41,17 @@
"params": [ "params": [
{"name": "url", "type": "string"} {"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"}
]
}] }]

4
tsconfig.json

@ -21,7 +21,7 @@
// "noEmit": true, /* Do not emit outputs. */ // "noEmit": true, /* Do not emit outputs. */
//"importHelpers": true, /* Import emit helpers from 'tslib'. */ //"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'. */ "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 Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */ "strict": true, /* Enable all strict type-checking options. */
@ -30,6 +30,7 @@
// "strictFunctionTypes": true, /* Enable strict checking of function types. */ // "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
"strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */ "strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */
// "skipLibCheck": true,
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ // "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. */ // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
@ -66,6 +67,7 @@
"./public/recorder.min.js", "./public/recorder.min.js",
"public", "public",
"coverage", "coverage",
"./src/scripts",
"./public/*.js", "./public/*.js",
"./src/lib/crypto/crypto.worker.js", "./src/lib/crypto/crypto.worker.js",
"./src/vendor/StackBlur.js", "./src/vendor/StackBlur.js",

Loading…
Cancel
Save