Browse Source

Chat list new search (alpha)

master
Eduard Kuzmenko 4 years ago
parent
commit
d9cd7b217e
  1. 15
      src/components/appMediaPlaybackController.ts
  2. 93
      src/components/appMediaViewer.ts
  3. 123
      src/components/appSearch.ts
  4. 593
      src/components/appSearchSuper..ts
  5. 44
      src/components/audio.ts
  6. 15
      src/components/avatar.ts
  7. 22
      src/components/chat/bubbles.ts
  8. 8
      src/components/chat/pinnedMessage.ts
  9. 66
      src/components/horizontalMenu.ts
  10. 2
      src/components/inputSearch.ts
  11. 314
      src/components/sidebarLeft/index.ts
  12. 29
      src/components/sidebarRight/tabs/sharedMedia.ts
  13. 26
      src/components/wrappers.ts
  14. 49
      src/lib/appManagers/appMessagesManager.ts
  15. 2
      src/lib/appManagers/appPeersManager.ts
  16. 8
      src/scss/partials/_chatPinned.scss
  17. 81
      src/scss/partials/_rightSidebar.scss
  18. 2
      src/scss/partials/_slider.scss

15
src/components/appMediaPlaybackController.ts

@ -199,10 +199,17 @@ class AppMediaPlaybackController { @@ -199,10 +199,17 @@ class AppMediaPlaybackController {
const media = this.playingMedia;
this.prevMid = this.nextMid = 0;
return appMessagesManager.getSearch(peerId, '', {
//_: type == 'audio' ? 'inputMessagesFilterMusic' : (type == 'round' ? 'inputMessagesFilterRoundVideo' : 'inputMessagesFilterVoice')
_: type == 'audio' ? 'inputMessagesFilterMusic' : 'inputMessagesFilterRoundVoice'
}, mid, 3, 0, 2).then(value => {
return appMessagesManager.getSearchNew({
peerId,
query: '',
inputFilter: {
//_: type == 'audio' ? 'inputMessagesFilterMusic' : (type == 'round' ? 'inputMessagesFilterRoundVideo' : 'inputMessagesFilterVoice')
_: type == 'audio' ? 'inputMessagesFilterMusic' : 'inputMessagesFilterRoundVoice'
},
maxId: mid,
limit: 3,
backLimit: 2
}).then(value => {
if(this.playingMedia !== media) {
return;
}

93
src/components/appMediaViewer.ts

@ -26,6 +26,7 @@ import Scrollable from "./scrollable"; @@ -26,6 +26,7 @@ import Scrollable from "./scrollable";
import appSidebarRight, { AppSidebarRight } from "./sidebarRight";
import SwipeHandler from "./swipeHandler";
import { months, ONE_DAY } from "../helpers/date";
import { SearchSuperContext } from "./appSearchSuper.";
// TODO: масштабирование картинок (не SVG) при ресайзе, и правильный возврат на исходную позицию
// TODO: картинки "обрезаются" если возвращаются или появляются с места, где есть их перекрытие (топбар, поле ввода)
@ -804,7 +805,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType @@ -804,7 +805,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
prevTargets: TargetType[] = [], nextTargets: TargetType[] = [], needLoadMore = true) {
if(this.setMoverPromise) return this.setMoverPromise;
this.log('openMedia:', media, fromId);
this.log('openMedia:', media, fromId, prevTargets, nextTargets);
this.setAuthorInfo(fromId, timestamp);
@ -1105,14 +1106,15 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType @@ -1105,14 +1106,15 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
type AppMediaViewerTargetType = {
element: HTMLElement,
mid: number
mid: number,
peerId: number
};
export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delete' | 'forward', AppMediaViewerTargetType> {
public currentMessageId = 0;
public peerId: number;
public threadId: number;
public currentPeerId = 0;
public searchContext: SearchSuperContext;
constructor(private inputFilter: 'inputMessagesFilterPhotoVideo' | 'inputMessagesFilterChatPhotos' = 'inputMessagesFilterPhotoVideo') {
constructor() {
super(['delete', 'forward']);
/* const stub = document.createElement('div');
@ -1165,27 +1167,29 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet @@ -1165,27 +1167,29 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
} */
onPrevClick = (target: AppMediaViewerTargetType) => {
this.nextTargets.unshift({element: this.lastTarget, mid: this.currentMessageId});
this.openMedia(appMessagesManager.getMessageByPeer(this.peerId, target.mid), target.element);
this.nextTargets.unshift({element: this.lastTarget, mid: this.currentMessageId, peerId: this.currentPeerId});
this.openMedia(appMessagesManager.getMessageByPeer(target.peerId, target.mid), target.element, -1);
};
onNextClick = (target: AppMediaViewerTargetType) => {
this.prevTargets.push({element: this.lastTarget, mid: this.currentMessageId});
this.openMedia(appMessagesManager.getMessageByPeer(this.peerId, target.mid), target.element);
this.prevTargets.push({element: this.lastTarget, mid: this.currentMessageId, peerId: this.currentPeerId});
this.openMedia(appMessagesManager.getMessageByPeer(target.peerId, target.mid), target.element, 1);
};
onForwardClick = () => {
if(this.currentMessageId) {
//appSidebarRight.forwardTab.open([this.currentMessageId]);
new PopupForward(this.peerId, [this.currentMessageId], () => {
new PopupForward(this.currentPeerId, [this.currentMessageId], () => {
return this.close();
});
}
};
onAuthorClick = (e: MouseEvent) => {
if(this.currentMessageId && this.currentMessageId != Number.MAX_SAFE_INTEGER) {
if(this.currentMessageId && this.currentMessageId !== Number.MAX_SAFE_INTEGER) {
const mid = this.currentMessageId;
const peerId = this.currentPeerId;
const threadId = this.searchContext.threadId;
this.close(e)
//.then(() => mediaSizes.isMobile ? appSidebarRight.sharedMediaTab.closeBtn.click() : Promise.resolve())
.then(() => {
@ -1193,14 +1197,14 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet @@ -1193,14 +1197,14 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
appSidebarRight.closeTab(AppSidebarRight.SLIDERITEMSIDS.sharedMedia);
}
const message = appMessagesManager.getMessageByPeer(this.peerId, mid);
appImManager.setInnerPeer(message.peerId, mid, this.threadId ? 'discussion' : undefined, this.threadId);
const message = appMessagesManager.getMessageByPeer(peerId, mid);
appImManager.setInnerPeer(message.peerId, mid, threadId ? 'discussion' : undefined, threadId);
});
}
};
onDownloadClick = () => {
const message = appMessagesManager.getMessageByPeer(this.peerId, this.currentMessageId);
const message = appMessagesManager.getMessageByPeer(this.currentPeerId, this.currentMessageId);
if(message.media.photo) {
appPhotosManager.savePhotoFile(message.media.photo, appImManager.chat.bubbles.lazyLoadQueue.queueId);
} else {
@ -1230,7 +1234,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet @@ -1230,7 +1234,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
const backLimit = older ? 0 : loadCount;
let maxId = this.currentMessageId;
let anchor: {element: HTMLElement, mid: number};
let anchor: AppMediaViewerTargetType;
if(older) {
anchor = this.reverse ? this.prevTargets[0] : this.nextTargets[this.nextTargets.length - 1];
} else {
@ -1240,18 +1244,25 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet @@ -1240,18 +1244,25 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
if(anchor) maxId = anchor.mid;
if(!older) maxId = appMessagesManager.incrementMessageId(maxId, 1);
const peerId = this.peerId;
const threadId = this.threadId;
const promise = appMessagesManager.getSearchNew({
peerId: this.searchContext.peerId,
query: this.searchContext.query,
inputFilter: {
_: this.searchContext.inputFilter
},
maxId,
limit: backLimit ? 0 : loadCount,
backLimit,
threadId: this.searchContext.threadId,
folderId: this.searchContext.folderId,
nextRate: this.searchContext.nextRate
}).then(value => {
this.log('loaded more media by maxId:', maxId, value, older, this.reverse);
const promise = appMessagesManager.getSearch(peerId, '',
{_: this.inputFilter}, maxId, backLimit ? 0 : loadCount/* older ? loadCount : 0 */, 0, backLimit, threadId).then(value => {
if(this.peerId !== peerId || this.threadId !== threadId) {
this.log.warn('peer changed');
return;
if(value.next_rate) {
this.searchContext.nextRate = value.next_rate;
}
this.log('loaded more media by maxId:', maxId, value, older, this.reverse);
if(value.history.length < loadCount) {
/* if(this.reverse) {
if(older) this.loadedAllMediaUp = true;
@ -1264,13 +1275,13 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet @@ -1264,13 +1275,13 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
const method = older ? value.history.forEach : value.history.forEachReverse;
method.call(value.history, message => {
const mid = message.mid
const {mid, peerId} = message;
const media = this.getMediaFromMessage(message);
if(!media) return;
//if(media._ == 'document' && media.type != 'video') return;
const t = {element: null as HTMLElement, mid: mid};
const t = {element: null as HTMLElement, mid, peerId};
if(older) {
if(this.reverse) this.prevTargets.unshift(t);
else this.nextTargets.push(t);
@ -1311,26 +1322,30 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet @@ -1311,26 +1322,30 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
}
}
public async openMedia(message: any, target?: HTMLElement, reverse = false,
prevTargets: AppMediaViewer['prevTargets'] = [], nextTargets: AppMediaViewer['prevTargets'] = [], needLoadMore = true, threadId?: number) {
public setSearchContext(context: SearchSuperContext) {
this.searchContext = context;
if(this.searchContext.folderId !== undefined) {
this.loadedAllMediaUp = true;
if(this.searchContext.nextRate === undefined) {
this.loadedAllMediaDown = true;
}
}
return this;
}
public async openMedia(message: any, target?: HTMLElement, fromRight = 0, reverse = false,
prevTargets: AppMediaViewer['prevTargets'] = [], nextTargets: AppMediaViewer['prevTargets'] = [], needLoadMore = true) {
if(this.setMoverPromise) return this.setMoverPromise;
const mid = message.mid;
const fromId = message.fromId;
const media = this.getMediaFromMessage(message);
let fromRight = 0;
if(!this.isFirstOpen) {
//if(this.lastTarget === prevTarget) {
if(this.reverse) fromRight = this.currentMessageId < mid ? 1 : -1;
else fromRight = this.currentMessageId > mid ? 1 : -1;
} else {
this.reverse = reverse;
this.peerId = message.peerId;
this.threadId = threadId;
}
this.currentMessageId = mid;
this.currentPeerId = message.peerId;
const promise = super._openMedia(media, message.date, fromId, fromRight, target, reverse, prevTargets, nextTargets, needLoadMore);
this.setCaption(message);

123
src/components/appSearch.ts

@ -1,14 +1,7 @@ @@ -1,14 +1,7 @@
import appDialogsManager from "../lib/appManagers/appDialogsManager";
import Scrollable from "./scrollable";
import appUsersManager from "../lib/appManagers/appUsersManager";
import appPeersManager from '../lib/appManagers/appPeersManager';
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import { formatPhoneNumber } from "./misc";
import appChatsManager from "../lib/appManagers/appChatsManager";
import InputSearch from "./inputSearch";
import rootScope from "../lib/rootScope";
import { escapeRegExp } from "../helpers/string";
import searchIndexManager from "../lib/searchIndexManager";
export class SearchGroup {
container: HTMLDivElement;
@ -57,17 +50,12 @@ export class SearchGroup { @@ -57,17 +50,12 @@ export class SearchGroup {
}
}
/**
* * Saved будет использована только для вывода одного элемента - избранное
*/
type SearchGroupType = 'saved' | 'contacts' | 'globalContacts' | 'messages' | string;
export type SearchGroupType = 'contacts' | 'globalContacts' | 'messages' | string;
export default class AppSearch {
private minMsgId = 0;
private loadedCount = -1;
private foundCount = -1;
private offsetRate = 0;
private loadedContacts = false;
private searchPromise: Promise<void> = null;
private searchTimeout: number = 0;
@ -126,8 +114,6 @@ export default class AppSearch { @@ -126,8 +114,6 @@ export default class AppSearch {
this.minMsgId = 0;
this.loadedCount = -1;
this.foundCount = -1;
this.offsetRate = 0;
this.loadedContacts = false;
for(let i in this.searchGroups) {
this.searchGroups[i as SearchGroupType].clear();
@ -164,112 +150,36 @@ export default class AppSearch { @@ -164,112 +150,36 @@ export default class AppSearch {
const maxId = this.minMsgId || 0;
if(!this.peerId && !maxId && !this.loadedContacts) {
const renderedPeerIds: Set<number> = new Set();
const setResults = (results: number[], group: SearchGroup, showMembersCount = false) => {
results.forEach((peerId) => {
if(renderedPeerIds.has(peerId)) {
return;
}
renderedPeerIds.add(peerId);
const peer = appPeersManager.getPeer(peerId);
//////////this.log('contacts peer', peer);
const {dom} = appDialogsManager.addDialogNew({
dialog: peerId,
container: group.list,
drawStatus: false,
avatarSize: 48,
autonomous: group.autonomous
});
if(showMembersCount && (peer.participants_count || peer.participants)) {
const regExp = new RegExp(`(${escapeRegExp(query)}|${escapeRegExp(searchIndexManager.cleanSearchText(query))})`, 'gi');
dom.titleSpan.innerHTML = dom.titleSpan.innerHTML.replace(regExp, '<i>$1</i>');
dom.lastMessageSpan.innerText = appChatsManager.getChatMembersString(-peerId);
} else if(peerId == rootScope.myId) {
dom.lastMessageSpan.innerHTML = 'chat with yourself';
} else {
let username = appPeersManager.getPeerUsername(peerId);
if(!username) {
const user = appUsersManager.getUser(peerId);
if(user && user.phone) {
username = '+' + formatPhoneNumber(user.phone).formatted;
}
} else {
username = '@' + username;
}
dom.lastMessageSpan.innerHTML = '<i>' + username + '</i>';
}
});
group.toggle();
};
const onLoad = <T>(arg: T) => {
if(this.searchInput.value != query) {
return;
}
this.loadedContacts = true;
return arg;
};
appUsersManager.getContacts(query, true)
.then(onLoad)
.then((contacts) => {
if(contacts) {
setResults(contacts, this.searchGroups.contacts, true);
}
});
appUsersManager.searchContacts(query, 20)
.then(onLoad)
.then((contacts) => {
if(contacts) {
setResults(contacts.my_results, this.searchGroups.contacts, true);
setResults(contacts.results, this.searchGroups.globalContacts);
}
});
appMessagesManager.getConversations(query, 0, 20, 0)
.then(onLoad)
.then(value => {
if(value) {
setResults(value.dialogs.map(d => d.peerId), this.searchGroups.contacts, true);
}
});
}
return this.searchPromise = appMessagesManager.getSearch(this.peerId, query, {_: 'inputMessagesFilterEmpty'}, maxId, 20, this.offsetRate, undefined, this.threadId).then(res => {
return this.searchPromise = appMessagesManager.getSearchNew({
peerId: this.peerId,
query,
inputFilter: {_: 'inputMessagesFilterEmpty'},
maxId,
limit: 20,
threadId: this.threadId
}).then(res => {
this.searchPromise = null;
if(this.searchInput.value != query) {
if(this.searchInput.value !== query) {
return;
}
//console.log('input search result:', this.peerId, query, null, maxId, 20, res);
const {count, history, next_rate} = res;
const {count, history} = res;
if(history.length && history[0].mid == this.minMsgId) {
if(history.length && history[0].mid === this.minMsgId) {
history.shift();
}
const searchGroup = this.searchGroups.messages;
history.forEach((message: any) => {
history.forEach((message) => {
const {dialog, dom} = appDialogsManager.addDialogNew({
dialog: message.peerId,
container: this.scrollable/* searchGroup.list */,
drawStatus: false,
avatarSize: 48
avatarSize: 54
});
appDialogsManager.setLastMessage(dialog, message, dom, query);
});
@ -277,14 +187,13 @@ export default class AppSearch { @@ -277,14 +187,13 @@ export default class AppSearch {
searchGroup.toggle();
this.minMsgId = history.length && history[history.length - 1].mid;
this.offsetRate = next_rate;
if(this.loadedCount == -1) {
if(this.loadedCount === -1) {
this.loadedCount = 0;
}
this.loadedCount += history.length;
if(this.foundCount == -1) {
if(this.foundCount === -1) {
this.foundCount = count;
this.onSearch && this.onSearch(this.foundCount);
}

593
src/components/appSearchSuper..ts

@ -1,21 +1,45 @@ @@ -1,21 +1,45 @@
import { limitSymbols } from "../helpers/string";
import appMessagesManager, { MyInputMessagesFilter } from "../lib/appManagers/appMessagesManager";
import { formatDateAccordingToToday, months } from "../helpers/date";
import { findUpClassName, positionElementByIndex } from "../helpers/dom";
import { copy, getObjectKeysAndSort } from "../helpers/object";
import { escapeRegExp, limitSymbols } from "../helpers/string";
import appChatsManager from "../lib/appManagers/appChatsManager";
import appDialogsManager from "../lib/appManagers/appDialogsManager";
import appMessagesManager, { MyInputMessagesFilter, MyMessage } from "../lib/appManagers/appMessagesManager";
import appPeersManager from "../lib/appManagers/appPeersManager";
import appPhotosManager from "../lib/appManagers/appPhotosManager";
import appStateManager from "../lib/appManagers/appStateManager";
import appUsersManager from "../lib/appManagers/appUsersManager";
import { logger } from "../lib/logger";
import RichTextProcessor from "../lib/richtextprocessor";
import rootScope from "../lib/rootScope";
import searchIndexManager from "../lib/searchIndexManager";
import AppMediaViewer from "./appMediaViewer";
import { SearchGroup, SearchGroupType } from "./appSearch";
import { horizontalMenu } from "./horizontalMenu";
import LazyLoadQueue from "./lazyLoadQueue";
import { renderImageFromUrl, putPreloader } from "./misc";
import { renderImageFromUrl, putPreloader, formatPhoneNumber } from "./misc";
import { ripple } from "./ripple";
import Scrollable from "./scrollable";
import { wrapDocument } from "./wrappers";
const testScroll = false;
export type SearchSuperType = MyInputMessagesFilter/* | 'chats' */;
export type SearchSuperContext = {
peerId: number,
inputFilter: MyInputMessagesFilter,
query?: string,
maxId?: number,
folderId?: number,
threadId?: number,
date?: number,
nextRate?: number
};
export default class AppSearchSuper {
public tabs: {[t in MyInputMessagesFilter]: HTMLDivElement} = {} as any;
public tabs: {[t in SearchSuperType]: HTMLDivElement} = {} as any;
public type: MyInputMessagesFilter;
public type: SearchSuperType;
public tabSelected: HTMLElement;
public container: HTMLElement;
@ -23,23 +47,34 @@ export default class AppSearchSuper { @@ -23,23 +47,34 @@ export default class AppSearchSuper {
private tabsMenu: HTMLUListElement;
private prevTabId = -1;
public mediaDivsByIds: {[mid: number]: HTMLDivElement} = {};
private lazyLoadQueue = new LazyLoadQueue();
private cleanupObj = {cleaned: false};
public historyStorage: Partial<{[type in MyInputMessagesFilter]: number[]}> = {};
public usedFromHistory: Partial<{[type in MyInputMessagesFilter]: number}> = {};
public historyStorage: Partial<{[type in SearchSuperType]: {mid: number, peerId: number}[]}> = {};
public usedFromHistory: Partial<{[type in SearchSuperType]: number}> = {};
public urlsToRevoke: string[] = [];
private peerId = 0;
private threadId = 0;
private query = '';
private searchContext: SearchSuperContext;
public loadMutex: Promise<any> = Promise.resolve();
private loadPromises: Partial<{[type in MyInputMessagesFilter]: Promise<void>}> = {};
private loaded: Partial<{[type in MyInputMessagesFilter]: boolean}> = {};
private nextRates: Partial<{[type in SearchSuperType]: number}> = {};
private loadPromises: Partial<{[type in SearchSuperType]: Promise<void>}> = {};
private loaded: Partial<{[type in SearchSuperType]: boolean}> = {};
private loadedChats = false;
constructor(public types: {inputFilter: MyInputMessagesFilter, name: string}[], public scrollable: Scrollable) {
private log = logger('SEARCH-SUPER');
public selectTab: ReturnType<typeof horizontalMenu>;
private monthContainers: Partial<{
[type in SearchSuperType]: {
[timestamp: number]: {
container: HTMLElement,
items: HTMLElement
}
}
}> = {};
constructor(public types: {inputFilter: SearchSuperType, name: string}[], public scrollable: Scrollable, public searchGroups?: {[group in SearchGroupType]: SearchGroup}, public asChatList = false) {
this.container = document.createElement('div');
this.container.classList.add('search-super');
@ -92,24 +127,24 @@ export default class AppSearchSuper { @@ -92,24 +127,24 @@ export default class AppSearchSuper {
};
//this.scroll.attachSentinels(undefined, 400);
horizontalMenu(this.tabsMenu, this.tabsContainer, (id, tabContent) => {
if(this.prevTabId == id) return;
this.selectTab = horizontalMenu(this.tabsMenu, this.tabsContainer, (id, tabContent) => {
if(this.prevTabId === id) return;
if(this.prevTabId != -1) {
if(this.prevTabId !== -1) {
this.onTransitionStart();
}
this.type = this.types[id].inputFilter;
this.tabSelected = tabContent.firstElementChild as HTMLDivElement;
if(this.prevTabId != -1 && nav.offsetTop) {
if(this.prevTabId !== -1 && nav.offsetTop) {
this.scrollable.scrollTop -= nav.offsetTop;
}
/* this.log('setVirtualContainer', id, this.sharedMediaSelected, this.sharedMediaSelected.childElementCount);
this.scroll.setVirtualContainer(this.sharedMediaSelected); */
if(this.prevTabId != -1 && !this.tabSelected.childElementCount) { // quick brown fix
if(this.prevTabId !== -1 && !this.tabSelected.childElementCount) { // quick brown fix
//this.contentContainer.classList.remove('loaded');
this.load(true);
}
@ -121,27 +156,30 @@ export default class AppSearchSuper { @@ -121,27 +156,30 @@ export default class AppSearchSuper {
});
this.tabs.inputMessagesFilterPhotoVideo.addEventListener('click', (e) => {
const target = e.target as HTMLDivElement;
const target = findUpClassName(e.target as HTMLDivElement, 'grid-item');
const messageId = +target.dataset.mid;
if(!messageId) {
console.warn('no messageId by click on target:', target);
const mid = +target.dataset.mid;
if(!mid) {
this.log.warn('no messageId by click on target:', target);
return;
}
const message = appMessagesManager.getMessageByPeer(this.peerId, messageId);
const ids = Object.keys(this.mediaDivsByIds).map(k => +k).sort((a, b) => a - b);
const idx = ids.findIndex(i => i == messageId);
const targets = ids.map(id => {
const element = this.mediaDivsByIds[id] as HTMLElement;
//element = element.querySelector('img') || element;
return {element, mid: id};
const peerId = +target.dataset.peerId;
const targets = (Array.from(this.tabs.inputMessagesFilterPhotoVideo.querySelectorAll('.grid-item')) as HTMLElement[]).map(el => {
return {element: el, mid: +el.dataset.mid, peerId: +el.dataset.peerId};
});
//const ids = Object.keys(this.mediaDivsByIds).map(k => +k).sort((a, b) => a - b);
const idx = targets.findIndex(item => item.mid === mid && item.peerId === peerId);
new AppMediaViewer().openMedia(message, target, false, targets.slice(idx + 1).reverse(), targets.slice(0, idx).reverse(), true/* , this.threadId */);
const message = appMessagesManager.getMessageByPeer(peerId, mid);
new AppMediaViewer()
.setSearchContext(this.copySearchContext(this.type))
.openMedia(message, target, 0, false, targets.slice(0, idx), targets.slice(idx + 1));
});
this.type = this.types[0].inputFilter;
}
private onTransitionStart = () => {
@ -163,18 +201,24 @@ export default class AppSearchSuper { @@ -163,18 +201,24 @@ export default class AppSearchSuper {
this.container.classList.remove('sliding');
};
public filterMessagesByType(ids: number[], type: MyInputMessagesFilter) {
let messages: any[] = [];
public filterMessagesByType(messages: any[], type: SearchSuperType): MyMessage[] {
if(type === 'inputMessagesFilterEmpty') return messages;
if(type != 'inputMessagesFilterUrl') {
for(let mid of ids) {
let message = appMessagesManager.getMessageByPeer(this.peerId, mid);
if(message.media) messages.push(message);
}
} else {
messages = ids.slice().map(mid => appMessagesManager.getMessageByPeer(this.peerId, mid));
if(type !== 'inputMessagesFilterUrl') {
messages = messages.filter(message => !!message.media);
}
/* if(!this.peerId) {
messages = messages.filter(message => {
if(message.peerId === rootScope.myId) {
return true;
}
const dialog = appMessagesManager.getDialogByPeerId(message.fromId)[0];
return dialog && dialog.folder_id === 0;
});
} */
let filtered: any[] = [];
switch(type) {
@ -186,7 +230,7 @@ export default class AppSearchSuper { @@ -186,7 +230,7 @@ export default class AppSearchSuper {
continue;
}
if(media._ == 'document' && media.type != 'video'/* && media.type != 'gif' */) {
if(media._ === 'document' && media.type !== 'video'/* && media.type !== 'gif' */) {
//this.log('broken video', media);
continue;
}
@ -209,9 +253,9 @@ export default class AppSearchSuper { @@ -209,9 +253,9 @@ export default class AppSearchSuper {
}
case 'inputMessagesFilterUrl': {
console.log('inputMessagesFilterUrl', messages);
//this.log('inputMessagesFilterUrl', messages);
for(let message of messages) {
//if((message.media.webpage && message.media.webpage._ != 'webPageEmpty')) {
//if((message.media.webpage && message.media.webpage._ !== 'webPageEmpty')) {
filtered.push(message);
//}
}
@ -221,7 +265,19 @@ export default class AppSearchSuper { @@ -221,7 +265,19 @@ export default class AppSearchSuper {
case 'inputMessagesFilterMusic': {
for(let message of messages) {
if(!message.media.document || message.media.document.type != 'audio') {
if(!message.media.document || message.media.document.type !== 'audio') {
continue;
}
filtered.push(message);
}
break;
}
case 'inputMessagesFilterVoice': {
for(let message of messages) {
if(!message.media.document || message.media.document.type !== 'voice') {
continue;
}
@ -238,31 +294,39 @@ export default class AppSearchSuper { @@ -238,31 +294,39 @@ export default class AppSearchSuper {
return filtered;
}
public async performSearchResult(messages: any[], type: MyInputMessagesFilter, append = true) {
const peerId = this.peerId;
const threadId = this.threadId;
const elemsToAppend: HTMLElement[] = [];
public async performSearchResult(messages: any[], type: SearchSuperType, append = true) {
const elemsToAppend: {element: HTMLElement, message: any}[] = [];
const promises: Promise<any>[] = [];
const sharedMediaDiv = this.tabs[type];
const sharedMediaDiv: HTMLElement = type === 'inputMessagesFilterEmpty' ? this.searchGroups.messages.list : this.tabs[type];
/* for(let contentType in contentToSharedMap) {
if(contentToSharedMap[contentType as ContentType] == type) {
sharedMediaDiv = this.sharedMedia[contentType as ContentType];
}
} */
const middleware = this.getMiddleware();
// https://core.telegram.org/type/MessagesFilter
switch(type) {
case 'inputMessagesFilterEmpty': {
for(const message of messages) {
const {dialog, dom} = appDialogsManager.addDialogNew({
dialog: message.peerId,
container: sharedMediaDiv as HTMLUListElement/* searchGroup.list */,
drawStatus: false,
avatarSize: 54
});
appDialogsManager.setLastMessage(dialog, message, dom, this.searchContext.query);
}
this.searchGroups.messages.setActive();
break;
}
case 'inputMessagesFilterPhotoVideo': {
for(const message of messages) {
const media = message.media.photo || message.media.document || (message.media.webpage && message.media.webpage.document);
const div = document.createElement('div');
div.classList.add('grid-item');
div.dataset.mid = '' + message.mid;
//console.log(message, photo);
//this.log(message, photo);
const isPhoto = media._ == 'photo';
const isPhoto = media._ === 'photo';
const photo = isPhoto ? appPhotosManager.getPhoto(media.id) : null;
let isDownloaded: boolean;
@ -280,7 +344,7 @@ export default class AppSearchSuper { @@ -280,7 +344,7 @@ export default class AppSearchSuper {
span.classList.add('video-time');
div.append(span);
if(media.type != 'gif') {
if(media.type !== 'gif') {
span.innerText = (media.duration + '').toHHMMSS(false);
/* const spanPlay = document.createElement('span');
@ -293,8 +357,8 @@ export default class AppSearchSuper { @@ -293,8 +357,8 @@ export default class AppSearchSuper {
const load = () => appPhotosManager.preloadPhoto(isPhoto ? media.id : media, appPhotosManager.choosePhotoSize(media, 200, 200))
.then(() => {
if(this.peerId != peerId || this.threadId != threadId) {
console.warn('peer changed');
if(!middleware()) {
//this.log.warn('peer changed');
return;
}
@ -343,7 +407,7 @@ export default class AppSearchSuper { @@ -343,7 +407,7 @@ export default class AppSearchSuper {
});
const timeout = setTimeout(() => {
//console.log('didn\'t load', thumb, media, isDownloaded, sizes);
//this.log('didn\'t load', thumb, media, isDownloaded, sizes);
reject();
}, 1e3);
});
@ -356,26 +420,28 @@ export default class AppSearchSuper { @@ -356,26 +420,28 @@ export default class AppSearchSuper {
else this.lazyLoadQueue.push({div, load});
}
elemsToAppend.push(div);
this.mediaDivsByIds[message.mid] = div;
elemsToAppend.push({element: div, message});
}
break;
}
case 'inputMessagesFilterVoice':
case 'inputMessagesFilterMusic':
case 'inputMessagesFilterDocument': {
for(const message of messages) {
const div = wrapDocument({
message,
withTime: true,
fontWeight: 400
withTime: !this.asChatList,
fontWeight: 400,
voiceAsMusic: true,
showSender: this.asChatList,
searchContext: this.copySearchContext(type)
});
if(message.media.document.type === 'audio') {
div.classList.add('audio-48');
}
div.dataset.mid = '' + message.mid;
elemsToAppend.push(div);
elemsToAppend.push({element: div, message});
}
break;
}
@ -432,7 +498,6 @@ export default class AppSearchSuper { @@ -432,7 +498,6 @@ export default class AppSearchSuper {
}
let div = document.createElement('div');
div.dataset.mid = '' + message.mid;
let previewDiv = document.createElement('div');
previewDiv.classList.add('preview');
@ -444,8 +509,8 @@ export default class AppSearchSuper { @@ -444,8 +509,8 @@ export default class AppSearchSuper {
if(webpage.photo) {
let load = () => appPhotosManager.preloadPhoto(webpage.photo.id, appPhotosManager.choosePhotoSize(webpage.photo, 60, 60))
.then(() => {
if(this.peerId != peerId || this.threadId != threadId) {
console.warn('peer changed');
if(!middleware()) {
//this.log.warn('peer changed');
return;
}
@ -467,19 +532,23 @@ export default class AppSearchSuper { @@ -467,19 +532,23 @@ export default class AppSearchSuper {
title = RichTextProcessor.wrapPlainText(webpage.display_url.split('/', 1)[0]);
}
/* if(webpage.description?.includes('Еще в начале')) {
console.error('FROM THE START', webpage);
} */
let sender = this.asChatList ? `<div class="subtitle sender">${appMessagesManager.getSenderToPeerText(message)}</div>` : '';
let titleAdditionHTML = '';
if(this.asChatList) {
titleAdditionHTML = `<div class="sent-time">${formatDateAccordingToToday(new Date(message.date * 1000))}</div>`;
}
div.append(previewDiv);
div.insertAdjacentHTML('beforeend', `
<div class="title">${title}</button>
<div class="title">${title}${titleAdditionHTML}</div>
<div class="subtitle">${subtitle}</div>
<div class="url">${url}</div>
${sender}
`);
if(div.innerText.trim().length) {
elemsToAppend.push(div);
elemsToAppend.push({element: div, message});
}
}
@ -488,7 +557,7 @@ export default class AppSearchSuper { @@ -488,7 +557,7 @@ export default class AppSearchSuper {
}
default:
//console.warn('death is my friend', messages);
//this.log.warn('death is my friend', messages);
break;
}
@ -498,25 +567,39 @@ export default class AppSearchSuper { @@ -498,25 +567,39 @@ export default class AppSearchSuper {
if(promises.length) {
await Promise.all(promises);
if(this.peerId !== peerId || this.threadId !== threadId) {
console.warn('peer changed');
if(!middleware()) {
//this.log.warn('peer changed');
return;
}
}
if(elemsToAppend.length) {
sharedMediaDiv[append ? 'append' : 'prepend'](...elemsToAppend);
const method = append ? 'append' : 'prepend';
elemsToAppend.forEach(details => {
const {element, message} = details;
const monthContainer = this.getMonthContainerByTimestamp(message.date, type);
element.classList.add('search-super-item');
element.dataset.mid = '' + message.mid;
element.dataset.peerId = '' + message.peerId;
monthContainer.items[method](element);
});
}
if(sharedMediaDiv) {
const parent = sharedMediaDiv.parentElement;
if(type !== 'inputMessagesFilterEmpty') {
this.afterPerforming(messages.length, sharedMediaDiv);
}
}
private afterPerforming(length: number, tab: HTMLElement) {
if(tab) {
const parent = tab.parentElement;
Array.from(parent.children).slice(1).forEach(child => {
child.remove();
});
//this.contentContainer.classList.add('loaded');
if(!messages.length && !sharedMediaDiv.childElementCount) {
if(!length && !tab.childElementCount) {
const div = document.createElement('div');
div.innerText = 'Nothing interesting here yet...';
div.classList.add('position-center', 'text-center', 'content-empty', 'no-select');
@ -525,20 +608,166 @@ export default class AppSearchSuper { @@ -525,20 +608,166 @@ export default class AppSearchSuper {
}
}
}
private loadChats() {
const renderedPeerIds: Set<number> = new Set();
const middleware = this.getMiddleware();
for(let i in this.searchGroups) {
const group = this.searchGroups[i as SearchGroupType];
this.tabs.inputMessagesFilterEmpty.append(group.container);
group.clear();
}
const query = this.searchContext.query;
if(query) {
const setResults = (results: number[], group: SearchGroup, showMembersCount = false) => {
results.forEach((peerId) => {
if(renderedPeerIds.has(peerId)) {
return;
}
renderedPeerIds.add(peerId);
const peer = appPeersManager.getPeer(peerId);
//////////this.log('contacts peer', peer);
const {dom} = appDialogsManager.addDialogNew({
dialog: peerId,
container: group.list,
drawStatus: false,
avatarSize: 48,
autonomous: group.autonomous
});
if(showMembersCount && (peer.participants_count || peer.participants)) {
const regExp = new RegExp(`(${escapeRegExp(query)}|${escapeRegExp(searchIndexManager.cleanSearchText(query))})`, 'gi');
dom.titleSpan.innerHTML = dom.titleSpan.innerHTML.replace(regExp, '<i>$1</i>');
dom.lastMessageSpan.innerText = appChatsManager.getChatMembersString(-peerId);
} else if(peerId === rootScope.myId) {
dom.lastMessageSpan.innerHTML = 'chat with yourself';
} else {
let username = appPeersManager.getPeerUsername(peerId);
if(!username) {
const user = appUsersManager.getUser(peerId);
if(user && user.phone) {
username = '+' + formatPhoneNumber(user.phone).formatted;
}
} else {
username = '@' + username;
}
dom.lastMessageSpan.innerHTML = '<i>' + username + '</i>';
}
});
group.toggle();
};
const onLoad = <T>(arg: T) => {
if(!middleware()) {
return;
}
//this.loadedContacts = true;
return arg;
};
return Promise.all([
appUsersManager.getContacts(query, true)
.then(onLoad)
.then((contacts) => {
if(contacts) {
setResults(contacts, this.searchGroups.contacts, true);
}
}),
appUsersManager.searchContacts(query, 20)
.then(onLoad)
.then((contacts) => {
if(contacts) {
setResults(contacts.my_results, this.searchGroups.contacts, true);
setResults(contacts.results, this.searchGroups.globalContacts);
}
}),
appMessagesManager.getConversations(query, 0, 20, 0)
.then(onLoad)
.then(value => {
if(value) {
setResults(value.dialogs.map(d => d.peerId), this.searchGroups.contacts, true);
}
})
]);
} else {
const renderRecentSearch = (setActive = true) => {
return appStateManager.getState().then(state => {
if(!middleware()) {
return;
}
this.searchGroups.recent.list.innerHTML = '';
state.recentSearch.slice(0, 20).forEach(peerId => {
let {dialog, dom} = appDialogsManager.addDialogNew({
dialog: peerId,
container: this.searchGroups.recent.list,
drawStatus: false,
meAsSaved: true,
avatarSize: 48,
autonomous: false
});
dom.lastMessageSpan.innerText = peerId > 0 ? appUsersManager.getUserStatusString(peerId) : appChatsManager.getChatMembersString(peerId);
});
if(!state.recentSearch.length) {
this.searchGroups.recent.clear();
} else if(setActive) {
this.searchGroups.recent.setActive();
}
});
};
return Promise.all([
appUsersManager.getTopPeers().then(peers => {
if(!middleware()) return;
//console.log('got top categories:', categories);
if(peers.length) {
peers.forEach((peerId) => {
appDialogsManager.addDialogNew({
dialog: peerId,
container: this.searchGroups.people.list,
drawStatus: false,
onlyFirstName: true,
avatarSize: 54,
autonomous: false
});
});
}
this.searchGroups.people.setActive();
}),
renderRecentSearch()
]);
}
}
public load(single = false, justLoad = false) {
if(testScroll/* || 1 == 1 */) {
return;
}
console.log('loadSidebarMedia', single, this.peerId, this.loadPromises);
const peerId = this.peerId;
const threadId = this.threadId;
const peerId = this.searchContext.peerId;
this.log('load', single, peerId, this.loadPromises);
let typesToLoad = single ? [this.type] : this.types.filter(t => t.inputFilter !== this.type && t.inputFilter !== 'inputMessagesFilterEmpty').map(t => t.inputFilter);
let typesToLoad = single ? [this.type] : this.types.filter(t => t.inputFilter !== this.type).map(t => t.inputFilter);
typesToLoad = typesToLoad.filter(type => !this.loaded[type]
|| this.usedFromHistory[type] < this.historyStorage[type].length);
|| (this.historyStorage[type] && this.usedFromHistory[type] < this.historyStorage[type].length));
if(!typesToLoad.length) return;
@ -546,12 +775,26 @@ export default class AppSearchSuper { @@ -546,12 +775,26 @@ export default class AppSearchSuper {
const historyStorage = this.historyStorage ?? (this.historyStorage = {});
const promises = typesToLoad.map(type => {
const middleware = this.getMiddleware();
const promises: Promise<any>[] = typesToLoad.map(type => {
if(this.loadPromises[type]) return this.loadPromises[type];
const history = historyStorage[type] ?? (historyStorage[type] = []);
const logStr = 'loadSidebarMedia [' + type + ']: ';
if(type === 'inputMessagesFilterEmpty' && !history.length) {
if(!this.loadedChats) {
this.loadChats();
this.loadedChats = true;
}
if(!this.searchContext.query.trim()) {
this.loaded[type] = true;
return Promise.resolve();
}
}
const logStr = 'load [' + type + ']: ';
// render from cache
if(history.length && this.usedFromHistory[type] < history.length && !justLoad) {
@ -561,11 +804,11 @@ export default class AppSearchSuper { @@ -561,11 +804,11 @@ export default class AppSearchSuper {
do {
let ids = history.slice(used, used + loadCount);
console.log(logStr + 'will render from cache', used, history, ids, loadCount);
//this.log(logStr + 'will render from cache', used, history, ids, loadCount);
used += ids.length;
slicedLength += ids.length;
messages.push(...this.filterMessagesByType(ids, type));
messages.push(...this.filterMessagesByType(ids.map(m => appMessagesManager.getMessageByPeer(m.peerId, m.mid)), type));
} while(slicedLength < loadCount && used < history.length);
// если перебор
@ -577,36 +820,49 @@ export default class AppSearchSuper { @@ -577,36 +820,49 @@ export default class AppSearchSuper {
this.usedFromHistory[type] = used;
//if(messages.length) {
return this.performSearchResult(messages, type);
return this.performSearchResult(messages, type).finally(() => {
setTimeout(() => {
this.scrollable.checkForTriggers();
}, 0);
});
//}
return Promise.resolve();
}
let maxId = history[history.length - 1] || 0;
let maxId = history.length ? history[history.length - 1].mid : 0;
console.log(logStr + 'search house of glass pre', type, maxId);
//this.log(logStr + 'search house of glass pre', type, maxId);
//let loadCount = history.length ? 50 : 15;
return this.loadPromises[type] = appMessagesManager.getSearch(peerId, '', {_: type}, maxId, loadCount, undefined, undefined/* , this.threadId */)
.then(value => {
const mids = value.history.map(message => message.mid);
history.push(...mids);
return this.loadPromises[type] = appMessagesManager.getSearchNew({
peerId,
query: this.searchContext.query,
inputFilter: {_: type},
maxId,
limit: loadCount,
nextRate: this.nextRates[type] ?? (this.nextRates[type] = 0),
threadId: this.searchContext.threadId,
folderId: this.searchContext.folderId
}).then(value => {
history.push(...value.history.map(m => ({mid: m.mid, peerId: m.peerId})));
console.log(logStr + 'search house of glass', type, value);
this.log(logStr + 'search house of glass', type, value);
if(this.peerId !== peerId || this.threadId !== threadId) {
//console.warn('peer changed');
if(!middleware()) {
//this.log.warn('peer changed');
return;
}
// ! Фикс случая, когда не загружаются документы при открытой панели разработчиков (происходит из-за того, что не совпадают критерии отбора документов в getSearch)
if(value.history.length < loadCount) {
//if((value.count || history.length == value.count) && history.length >= value.count) {
console.log(logStr + 'loaded all media', value, loadCount);
//this.log(logStr + 'loaded all media', value, loadCount);
this.loaded[type] = true;
}
this.nextRates[type] = value.next_rate;
if(justLoad) {
return Promise.resolve();
}
@ -616,46 +872,89 @@ export default class AppSearchSuper { @@ -616,46 +872,89 @@ export default class AppSearchSuper {
if(!this.loaded[type]) {
this.loadPromises[type].then(() => {
setTimeout(() => {
console.log('will preload more');
const promise = this.load(true, true);
if(promise) {
promise.then(() => {
console.log('preloaded more');
this.scrollable.checkForTriggers();
});
//this.log('will preload more');
if(this.type === type) {
const promise = this.load(true, true);
if(promise) {
promise.then(() => {
//this.log('preloaded more');
setTimeout(() => {
this.scrollable.checkForTriggers();
}, 0);
});
}
}
}, 0);
});
}
//if(value.history.length) {
return this.performSearchResult(this.filterMessagesByType(mids, type), type);
return this.performSearchResult(this.filterMessagesByType(value.history, type), type);
//}
}).catch(err => {
console.error('load error:', err);
this.log.error('load error:', err);
}).finally(() => {
this.loadPromises[type] = null;
});
});
return Promise.all(promises).catch(err => {
console.error('Load error all promises:', err);
this.log.error('Load error all promises:', err);
});
}
public getMonthContainerByTimestamp(timestamp: number, type: SearchSuperType) {
const date = new Date(timestamp * 1000);
date.setHours(0, 0, 0);
date.setDate(1);
const dateTimestamp = date.getTime();
const containers = this.monthContainers[type] ?? (this.monthContainers[type] = {});
if(!(dateTimestamp in containers)) {
const str = months[date.getMonth()] + ' ' + date.getFullYear();
const container = document.createElement('div');
container.className = 'search-super-month';
const name = document.createElement('div');
name.classList.add('search-super-month-name');
name.innerText = str;
container.append(name);
const items = document.createElement('div');
items.classList.add('search-super-month-items');
container.append(name, items);
const haveTimestamps = getObjectKeysAndSort(containers, 'desc');
let i = 0;
for(; i < haveTimestamps.length; ++i) {
const t = haveTimestamps[i];
if(dateTimestamp > t) {
break;
}
}
containers[dateTimestamp] = {container, items};
positionElementByIndex(container, this.tabs[type], i);
}
return containers[dateTimestamp];
}
public cleanup() {
this.loadPromises = {};
this.loaded = {};
this.loadedChats = false;
this.nextRates = {};
this.prevTabId = -1;
this.mediaDivsByIds = {};
this.lazyLoadQueue.clear();
this.types.forEach(type => {
this.usedFromHistory[type.inputFilter] = -1;
});
this.type = 'inputMessagesFilterPhotoVideo';
this.cleanupObj.cleaned = true;
this.cleanupObj = {cleaned: false};
}
public cleanupHTML() {
@ -666,11 +965,15 @@ export default class AppSearchSuper { @@ -666,11 +965,15 @@ export default class AppSearchSuper {
this.urlsToRevoke.length = 0;
}
(Object.keys(this.tabs) as MyInputMessagesFilter[]).forEach(sharedMediaType => {
this.tabs[sharedMediaType].innerHTML = '';
(Object.keys(this.tabs) as SearchSuperType[]).forEach(type => {
this.tabs[type].innerHTML = '';
if(type === 'inputMessagesFilterEmpty') {
return;
}
if(!this.historyStorage || !this.historyStorage[sharedMediaType]) {
const parent = this.tabs[sharedMediaType].parentElement;
if(!this.historyStorage || !this.historyStorage[type]) {
const parent = this.tabs[type].parentElement;
if(!testScroll) {
if(!parent.querySelector('.preloader')) {
putPreloader(parent, true);
@ -683,6 +986,8 @@ export default class AppSearchSuper { @@ -683,6 +986,8 @@ export default class AppSearchSuper {
}
}
});
this.monthContainers = {};
if(testScroll) {
for(let i = 0; i < 1500; ++i) {
@ -694,15 +999,39 @@ export default class AppSearchSuper { @@ -694,15 +999,39 @@ export default class AppSearchSuper {
this.tabs.inputMessagesFilterPhotoVideo.append(div);
}
}
}
// * will change .cleaned in cleanup() and new instance will be created
public getMiddleware() {
const cleanupObj = this.cleanupObj;
return () => {
return !cleanupObj.cleaned;
};
}
(this.tabsMenu.firstElementChild as HTMLLIElement).click(); // set media
private copySearchContext(newInputFilter: MyInputMessagesFilter) {
const context = copy(this.searchContext);
context.inputFilter = newInputFilter;
context.nextRate = this.nextRates[newInputFilter];
return context;
}
public setQuery(peerId: number, query: string, threadId: number, historyStorage: AppSearchSuper['historyStorage'] = {}) {
this.peerId = peerId;
this.query = query;
this.threadId = threadId;
this.historyStorage = historyStorage;
public setQuery({peerId, query, threadId, historyStorage, folderId}: {
peerId: number,
query?: string,
threadId?: number,
historyStorage?: AppSearchSuper['historyStorage'],
folderId?: number
}) {
this.searchContext = {
peerId: peerId || 0,
query: query || '',
inputFilter: this.type,
threadId,
folderId
};
this.historyStorage = historyStorage ?? {};
this.cleanup();
}

44
src/components/audio.ts

@ -12,6 +12,9 @@ import appMessagesManager from "../lib/appManagers/appMessagesManager"; @@ -12,6 +12,9 @@ import appMessagesManager from "../lib/appManagers/appMessagesManager";
import rootScope from "../lib/rootScope";
import './middleEllipsis';
import { attachClickEvent, cancelEvent, detachClickEvent } from "../helpers/dom";
import appPeersManager from "../lib/appManagers/appPeersManager";
import { SearchSuperContext } from "./appSearchSuper.";
import { formatDateAccordingToToday } from "../helpers/date";
rootScope.on('messages_media_read', e => {
const {mids, peerId} = e.detail;
@ -254,21 +257,39 @@ function wrapVoiceMessage(audioEl: AudioElement) { @@ -254,21 +257,39 @@ function wrapVoiceMessage(audioEl: AudioElement) {
function wrapAudio(audioEl: AudioElement) {
const withTime = audioEl.withTime;
const doc = audioEl.message.media.document || audioEl.message.media.webpage.document;
const title = doc.audioTitle || doc.file_name;
let subtitle = doc.audioPerformer ? RichTextProcessor.wrapPlainText(doc.audioPerformer) : '';
const message = audioEl.message;
const doc: MyDocument = message.media.document || message.media.webpage.document;
const senderTitle = audioEl.showSender ? appMessagesManager.getSenderToPeerText(message) : '';
if(withTime) {
subtitle += (subtitle ? ' • ' : '') + formatDate(doc.date);
} else if(!subtitle) {
subtitle = 'Unknown Artist';
let title = doc.type === 'voice' ? senderTitle : (doc.audioTitle || doc.file_name);
let subtitle: string;
if(doc.type === 'voice') {
subtitle = '';
} else {
subtitle = doc.audioPerformer ? RichTextProcessor.wrapPlainText(doc.audioPerformer) : '';
if(withTime) {
subtitle += (subtitle ? ' • ' : '') + formatDate(doc.date);
} else if(!subtitle) {
subtitle = 'Unknown Artist';
}
if(audioEl.showSender) {
subtitle += ' • ' + senderTitle;
} else {
subtitle = ' • ' + subtitle;
}
}
subtitle = ' • ' + subtitle;
let titleAdditionHTML = '';
if(audioEl.showSender) {
titleAdditionHTML = `<div class="sent-time">${formatDateAccordingToToday(new Date(message.date * 1000))}</div>`;
}
const html = `
<div class="audio-details">
<div class="audio-title"><middle-ellipsis-element data-font-weight="${audioEl.dataset.fontWeight}">${title}</middle-ellipsis-element></div>
<div class="audio-title"><middle-ellipsis-element data-font-weight="${audioEl.dataset.fontWeight}">${title}</middle-ellipsis-element>${titleAdditionHTML}</div>
<div class="audio-subtitle"><div class="audio-time"></div>${subtitle}</div>
</div>`;
@ -319,6 +340,9 @@ export default class AudioElement extends HTMLElement { @@ -319,6 +340,9 @@ export default class AudioElement extends HTMLElement {
public preloader: ProgressivePreloader;
public message: any;
public withTime = false;
public voiceAsMusic = false;
public searchContext: SearchSuperContext;
public showSender = false;
private attachedHandlers: {[name: string]: any[]} = {};
private onTypeDisconnect: () => void;
@ -332,7 +356,7 @@ export default class AudioElement extends HTMLElement { @@ -332,7 +356,7 @@ export default class AudioElement extends HTMLElement {
this.classList.add('audio');
const doc = this.message.media.document || this.message.media.webpage.document;
const isVoice = doc.type == 'voice';
const isVoice = !this.voiceAsMusic && doc.type == 'voice';
const uploading = this.message.pFlags.is_outgoing;
const durationStr = String(doc.duration | 0).toHHMMSS();

15
src/components/avatar.ts

@ -52,7 +52,13 @@ export default class AvatarElement extends HTMLElement { @@ -52,7 +52,13 @@ export default class AvatarElement extends HTMLElement {
if(peerId < 0) {
const maxId = Number.MAX_SAFE_INTEGER;
const inputFilter = 'inputMessagesFilterChatPhotos';
let message: any = await appMessagesManager.getSearch(peerId, '', {_: inputFilter}, maxId, 2, 0, 1).then(value => {
let message: any = await appMessagesManager.getSearchNew({
peerId,
inputFilter: {_: inputFilter},
maxId,
limit: 2,
backLimit: 1
}).then(value => {
//console.log(lol);
// ! by descend
return value.history[0];
@ -78,7 +84,12 @@ export default class AvatarElement extends HTMLElement { @@ -78,7 +84,12 @@ export default class AvatarElement extends HTMLElement {
}
const good = Array.from(this.querySelectorAll('img')).find(img => !img.classList.contains('emoji'));
new AppMediaViewer(inputFilter).openMedia(message, good ? this : null);
new AppMediaViewer()
.setSearchContext({
peerId,
inputFilter,
})
.openMedia(message, good ? this : null);
loading = false;
return;
}

22
src/components/chat/bubbles.ts

@ -586,7 +586,7 @@ export default class ChatBubbles { @@ -586,7 +586,7 @@ export default class ChatBubbles {
return;
}
let targets: {element: HTMLElement, mid: number}[] = [];
let targets: {element: HTMLElement, mid: number, peerId: number}[] = [];
let ids = Object.keys(this.bubbles).map(k => +k).filter(id => {
//if(!this.scrollable.visibleElements.find(e => e.element == this.bubbles[id])) return false;
@ -609,7 +609,8 @@ export default class ChatBubbles { @@ -609,7 +609,8 @@ export default class ChatBubbles {
let albumItem = findUpClassName(element, 'album-item');
targets.push({
element,
mid: +albumItem?.dataset.mid || id
mid: +albumItem?.dataset.mid || id,
peerId: this.peerId
});
});
});
@ -625,8 +626,13 @@ export default class ChatBubbles { @@ -625,8 +626,13 @@ export default class ChatBubbles {
return;
}
new AppMediaViewer().openMedia(message, targets[idx].element, true,
targets.slice(0, idx), targets.slice(idx + 1), undefined, this.chat.threadId/* , !message.grouped_id */);
new AppMediaViewer()
.setSearchContext({
threadId: this.chat.threadId,
peerId: this.peerId,
inputFilter: 'inputMessagesFilterPhotoVideo'
})
.openMedia(message, targets[idx].element, 0, true, targets.slice(0, idx), targets.slice(idx + 1));
cancelEvent(e);
//appMediaViewer.openMedia(message, target as HTMLImageElement);
@ -2344,7 +2350,13 @@ export default class ChatBubbles { @@ -2344,7 +2350,13 @@ export default class ChatBubbles {
if(this.chat.type === 'chat' || this.chat.type === 'discussion') {
return this.appMessagesManager.getHistory(this.peerId, maxId, loadCount, backLimit, this.chat.threadId);
} else if(this.chat.type === 'pinned') {
const promise = this.appMessagesManager.getSearch(this.peerId, '', {_: 'inputMessagesFilterPinned'}, maxId, loadCount, 0, backLimit)
const promise = this.appMessagesManager.getSearchNew({
peerId: this.peerId,
inputFilter: {_: 'inputMessagesFilterPinned'},
maxId,
limit: loadCount,
backLimit
})
.then(value => ({history: value.history.map(m => m.mid)}));
return promise;

8
src/components/chat/pinnedMessage.ts

@ -410,7 +410,13 @@ export default class ChatPinnedMessage { @@ -410,7 +410,13 @@ export default class ChatPinnedMessage {
try {
let gotRest = false;
const promises = [
this.appMessagesManager.getSearch(this.topbar.peerId, '', {_: 'inputMessagesFilterPinned'}, mid, ChatPinnedMessage.LOAD_COUNT, 0, ChatPinnedMessage.LOAD_COUNT)
this.appMessagesManager.getSearchNew({
peerId: this.topbar.peerId,
inputFilter: {_: 'inputMessagesFilterPinned'},
maxId: mid,
limit: ChatPinnedMessage.LOAD_COUNT,
backLimit: ChatPinnedMessage.LOAD_COUNT
})
.then(r => {
gotRest = true;
return r;

66
src/components/horizontalMenu.ts

@ -5,34 +5,21 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick? @@ -5,34 +5,21 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?
const selectTab = TransitionSlider(content, tabs || content.dataset.slider == 'tabs' ? 'tabs' : 'navigation', transitionTime, onTransitionEnd);
if(tabs) {
const useStripe = !tabs.classList.contains('no-stripe');
const tagName = tabs.classList.contains('menu-horizontal-div') ? 'BUTTON' : 'LI';//tabs.firstElementChild.tagName;
tabs.addEventListener('click', function(e) {
let target = e.target as HTMLElement;
if(target.tagName != tagName) {
target = findUpTag(target, tagName);
}
//console.log('tabs click:', target);
if(!target) return false;
let id: number;
if(target.dataset.tab) {
id = +target.dataset.tab;
if(id == -1) {
return false;
}
} else {
id = whichChild(target);
const proxy = new Proxy(selectTab, {
apply: (target, that, args) => {
const id = +args[0];
const animate = args[1] !== undefined ? args[1] : true;
const el = (tabs.querySelector(`[data-tab="${id}"]`) || tabs.children[id]) as HTMLElement;
selectTarget(el, id, animate);
}
});
const selectTarget = (target: HTMLElement, id: number, animate = true) => {
const tabContent = content.children[id] as HTMLDivElement;
if(onClick) onClick(id, tabContent);
if(target.classList.contains('active') || id == selectTab.prevId) {
if(target.classList.contains('active') || id === selectTab.prevId) {
return false;
}
@ -40,7 +27,7 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick? @@ -40,7 +27,7 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?
prev && prev.classList.remove('active');
// stripe from ZINCHUK
if(useStripe && selectTab.prevId != -1) {
if(useStripe && selectTab.prevId !== -1 && animate) {
const indicator = target.querySelector('i')!;
const currentIndicator = target.parentElement.children[selectTab.prevId].querySelector('i')!;
@ -63,8 +50,37 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick? @@ -63,8 +50,37 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?
// stripe END
target.classList.add('active');
selectTab(id);
selectTab(id, animate);
};
const useStripe = !tabs.classList.contains('no-stripe');
const tagName = tabs.classList.contains('menu-horizontal-div') ? 'BUTTON' : 'LI';//tabs.firstElementChild.tagName;
tabs.addEventListener('click', function(e) {
let target = e.target as HTMLElement;
if(target.tagName != tagName) {
target = findUpTag(target, tagName);
}
//console.log('tabs click:', target);
if(!target) return false;
let id: number;
if(target.dataset.tab) {
id = +target.dataset.tab;
if(id == -1) {
return false;
}
} else {
id = whichChild(target);
}
selectTarget(target, id);
});
return proxy;
}
return selectTab;

2
src/components/inputSearch.ts

@ -45,7 +45,7 @@ export default class InputSearch { @@ -45,7 +45,7 @@ export default class InputSearch {
//this.input.classList.toggle('is-empty', !value.trim());
if(value != this.prevValue) {
if(value !== this.prevValue) {
this.prevValue = value;
clearTimeout(this.timeout);
this.timeout = window.setTimeout(() => {

314
src/components/sidebarLeft/index.ts

@ -12,7 +12,7 @@ import { attachClickEvent, findUpClassName, findUpTag } from "../../helpers/dom" @@ -12,7 +12,7 @@ import { attachClickEvent, findUpClassName, findUpTag } from "../../helpers/dom"
import AppSearch, { SearchGroup } from "../appSearch";
import "../avatar";
import { parseMenuButtonsTo } from "../misc";
import { ScrollableX } from "../scrollable";
import Scrollable, { ScrollableX } from "../scrollable";
import InputSearch from "../inputSearch";
import SidebarSlider from "../slider";
import { TransitionSlider } from "../transition";
@ -28,6 +28,7 @@ import AppNewGroupTab from "./tabs/newGroup"; @@ -28,6 +28,7 @@ import AppNewGroupTab from "./tabs/newGroup";
import AppSettingsTab from "./tabs/settings";
import appMessagesManager from "../../lib/appManagers/appMessagesManager";
import apiManagerProxy from "../../lib/mtproto/mtprotoworker";
import AppSearchSuper from "../appSearchSuper.";
const newChannelTab = new AppNewChannelTab();
const addMembersTab = new AppAddMembersTab();
@ -39,30 +40,6 @@ const editFolderTab = new AppEditFolderTab(); @@ -39,30 +40,6 @@ const editFolderTab = new AppEditFolderTab();
const includedChatsTab = new AppIncludedChatsTab();
const archivedTab = new AppArchivedTab();
/* const Transition = (container: HTMLElement, duration: number, from: HTMLElement, to: HTMLElement) => {
if(to.classList.contains('active')) return Promise.resolve();
container.classList.add('animating');
const backwards = whichChild(to) < whichChild(from);
if(backwards) {
container.classList.add('backwards');
}
from.classList.add('from');
to.classList.add('to');
return new Promise((resolve) => {
setTimeout(() => {
from.classList.remove('from', 'active');
container.classList.remove('animating', 'backwards');
to.classList.replace('to', 'active');
resolve();
}, duration);
});
}; */
export class AppSidebarLeft extends SidebarSlider {
public static SLIDERITEMSIDS = {
archived: 1,
@ -79,7 +56,6 @@ export class AppSidebarLeft extends SidebarSlider { @@ -79,7 +56,6 @@ export class AppSidebarLeft extends SidebarSlider {
private toolsBtn: HTMLButtonElement;
private backBtn: HTMLButtonElement;
private searchContainer: HTMLDivElement;
//private searchInput = document.getElementById('global-search') as HTMLInputElement;
private inputSearch: InputSearch;
@ -115,12 +91,7 @@ export class AppSidebarLeft extends SidebarSlider { @@ -115,12 +91,7 @@ export class AppSidebarLeft extends SidebarSlider {
//private log = logger('SL');
private searchGroups: {[k in 'contacts' | 'globalContacts' | 'messages' | 'people' | 'recent']: SearchGroup} = {} as any;
private globalSearch: AppSearch;
// peerIds
private recentSearch: number[] = [];
private recentSearchLoaded = false;
private recentSearchClearBtn: HTMLElement;
searchSuper: AppSearchSuper;
constructor() {
super(document.getElementById('column-left') as HTMLDivElement);
@ -146,7 +117,6 @@ export class AppSidebarLeft extends SidebarSlider { @@ -146,7 +117,6 @@ export class AppSidebarLeft extends SidebarSlider {
this.toolsBtn = this.sidebarEl.querySelector('.sidebar-tools-button') as HTMLButtonElement;
this.backBtn = this.sidebarEl.querySelector('.sidebar-back-button') as HTMLButtonElement;
this.searchContainer = this.sidebarEl.querySelector('#search-container') as HTMLDivElement;
this.archivedTab = archivedTab;
this.newChannelTab = newChannelTab;
@ -161,138 +131,7 @@ export class AppSidebarLeft extends SidebarSlider { @@ -161,138 +131,7 @@ export class AppSidebarLeft extends SidebarSlider {
this.menuEl = this.toolsBtn.querySelector('.btn-menu');
this.newBtnMenu = this.sidebarEl.querySelector('#new-menu');
this.inputSearch.input.addEventListener('focus', () => {
this.searchGroups = {
//saved: new SearchGroup('', 'contacts'),
contacts: new SearchGroup('Chats', 'contacts'),
globalContacts: new SearchGroup('Global Search', 'contacts'),
messages: new SearchGroup('Global Search', 'messages'),
people: new SearchGroup('People', 'contacts', false, 'search-group-people', true, false),
recent: new SearchGroup('Recent', 'contacts', false, 'search-group-recent', true, false)
};
this.globalSearch = new AppSearch(this.searchContainer, this.inputSearch, this.searchGroups, (count) => {
if(!count && !this.inputSearch.value.trim()) {
this.globalSearch.reset();
this.searchGroups.people.toggle();
this.renderRecentSearch();
}
});
this.searchContainer.addEventListener('click', (e) => {
const target = findUpTag(e.target, 'LI') as HTMLElement;
if(!target) {
return;
}
const searchGroup = findUpClassName(target, 'search-group');
if(!searchGroup || searchGroup.classList.contains('search-group-recent') || searchGroup.classList.contains('search-group-people')) {
return;
}
const peerId = +target.getAttribute('data-peerId');
if(this.recentSearch[0] != peerId) {
this.recentSearch.findAndSplice(p => p == peerId);
this.recentSearch.unshift(peerId);
if(this.recentSearch.length > 20) {
this.recentSearch.length = 20;
}
this.renderRecentSearch();
appStateManager.pushToState('recentSearch', this.recentSearch);
for(const peerId of this.recentSearch) {
appStateManager.setPeer(peerId, appPeersManager.getPeer(peerId));
}
clearRecentSearchBtn.style.display = '';
}
}, {capture: true});
let peopleContainer = document.createElement('div');
peopleContainer.classList.add('search-group-scrollable');
peopleContainer.append(this.searchGroups.people.list);
this.searchGroups.people.container.append(peopleContainer);
let peopleScrollable = new ScrollableX(peopleContainer);
appUsersManager.getTopPeers().then(peers => {
//console.log('got top categories:', categories);
if(peers.length) {
peers.forEach((peerId) => {
appDialogsManager.addDialogNew({
dialog: peerId,
container: this.searchGroups.people.list,
drawStatus: false,
onlyFirstName: true,
avatarSize: 54,
autonomous: false
});
});
}
this.searchGroups.people.toggle();
});
let hideNewBtnMenuTimeout: number;
//const transition = Transition.bind(null, this.searchContainer.parentElement, 150);
const transition = TransitionSlider(this.searchContainer.parentElement, 'zoom-fade', 150, (id) => {
if(hideNewBtnMenuTimeout) clearTimeout(hideNewBtnMenuTimeout);
if(id == 0) {
this.globalSearch.reset();
hideNewBtnMenuTimeout = window.setTimeout(() => {
hideNewBtnMenuTimeout = 0;
this.newBtnMenu.classList.remove('is-hidden');
}, 150);
}
});
transition(0);
const onFocus = () => {
this.toolsBtn.classList.remove('active');
this.backBtn.classList.add('active');
this.newBtnMenu.classList.add('is-hidden');
transition(1);
if(firstTime) {
this.searchGroups.people.toggle();
this.renderRecentSearch();
firstTime = false;
}
/* this.searchInput.addEventListener('blur', (e) => {
if(!this.searchInput.value) {
this.toolsBtn.classList.add('active');
this.backBtn.classList.remove('active');
this.backBtn.click();
}
}, {once: true}); */
};
let firstTime = true;
this.inputSearch.input.addEventListener('focus', onFocus);
onFocus();
this.backBtn.addEventListener('click', (e) => {
//appDialogsManager.chatsArchivedContainer.classList.remove('active');
this.toolsBtn.classList.add('active');
this.backBtn.classList.remove('active');
firstTime = true;
transition(0);
});
this.renderRecentSearch();
const clearRecentSearchBtn = this.recentSearchClearBtn = document.createElement('button');
clearRecentSearchBtn.classList.add('btn-icon', 'tgico-close');
this.searchGroups.recent.nameEl.append(clearRecentSearchBtn);
clearRecentSearchBtn.addEventListener('click', () => {
this.recentSearch = [];
appStateManager.pushToState('recentSearch', this.recentSearch);
this.renderRecentSearch(false);
clearRecentSearchBtn.style.display = 'none';
});
}, {once: true});
this.inputSearch.input.addEventListener('focus', () => this.initSearch(), {once: true});
parseMenuButtonsTo(this.buttons, this.menuEl.children);
parseMenuButtonsTo(this.newButtons, this.newBtnMenu.firstElementChild.children);
@ -339,39 +178,134 @@ export class AppSidebarLeft extends SidebarSlider { @@ -339,39 +178,134 @@ export class AppSidebarLeft extends SidebarSlider {
appUsersManager.getTopPeers();
}
public renderRecentSearch(setActive = true) {
appStateManager.getState().then(state => {
if(state && !this.recentSearchLoaded && Array.isArray(state.recentSearch)) {
this.recentSearch = state.recentSearch;
this.recentSearchLoaded = true;
}
private initSearch() {
const searchContainer = this.sidebarEl.querySelector('#search-container') as HTMLDivElement;
const scrollable = new Scrollable(searchContainer);
this.searchGroups = {
contacts: new SearchGroup('Chats', 'contacts'),
globalContacts: new SearchGroup('Global Search', 'contacts'),
messages: new SearchGroup('Messages', 'messages'),
people: new SearchGroup('', 'contacts', true, 'search-group-people', true, false),
recent: new SearchGroup('Recent', 'contacts', true, 'search-group-recent', true, false)
};
const searchSuper = this.searchSuper = new AppSearchSuper([{
inputFilter: 'inputMessagesFilterEmpty',
name: 'Chats'
}, {
inputFilter: 'inputMessagesFilterPhotoVideo',
name: 'Media'
}, {
inputFilter: 'inputMessagesFilterUrl',
name: 'Links'
}, {
inputFilter: 'inputMessagesFilterDocument',
name: 'Files'
}, {
inputFilter: 'inputMessagesFilterMusic',
name: 'Music'
}, {
inputFilter: 'inputMessagesFilterVoice',
name: 'Voice'
}], scrollable, this.searchGroups, true);
scrollable.container.append(searchSuper.container);
searchSuper.setQuery({
peerId: 0,
folderId: 0
});
searchSuper.selectTab(0);
searchSuper.load(true);
this.inputSearch.onChange = (value) => {
searchSuper.cleanupHTML();
searchSuper.setQuery({
peerId: 0,
folderId: 0,
query: value
});
searchSuper.load(true);
};
if(this.inputSearch.value.trim()) {
searchSuper.tabs.inputMessagesFilterEmpty.addEventListener('click', (e) => {
const target = findUpTag(e.target, 'LI') as HTMLElement;
if(!target) {
return;
}
this.searchGroups.recent.list.innerHTML = '';
this.recentSearchClearBtn.style.display = this.recentSearch.length ? '' : 'none';
this.recentSearch.slice(0, 20).forEach(peerId => {
let {dialog, dom} = appDialogsManager.addDialogNew({
dialog: peerId,
container: this.searchGroups.recent.list,
drawStatus: false,
meAsSaved: true,
avatarSize: 48,
autonomous: false
});
const searchGroup = findUpClassName(target, 'search-group');
if(!searchGroup || searchGroup.classList.contains('search-group-recent') || searchGroup.classList.contains('search-group-people')) {
return;
}
dom.lastMessageSpan.innerText = peerId > 0 ? appUsersManager.getUserStatusString(peerId) : appChatsManager.getChatMembersString(peerId);
const peerId = +target.getAttribute('data-peerId');
appStateManager.getState().then(state => {
const recentSearch = state.recentSearch || [];
if(recentSearch[0] != peerId) {
recentSearch.findAndSplice(p => p == peerId);
recentSearch.unshift(peerId);
if(recentSearch.length > 20) {
recentSearch.length = 20;
}
appStateManager.pushToState('recentSearch', recentSearch);
for(const peerId of recentSearch) {
appStateManager.setPeer(peerId, appPeersManager.getPeer(peerId));
}
}
});
if(!this.recentSearch.length) {
this.searchGroups.recent.clear();
} else if(setActive) {
this.searchGroups.recent.setActive();
}, {capture: true});
let peopleContainer = document.createElement('div');
peopleContainer.classList.add('search-group-scrollable');
peopleContainer.append(this.searchGroups.people.list);
this.searchGroups.people.container.append(peopleContainer);
let peopleScrollable = new ScrollableX(peopleContainer);
let hideNewBtnMenuTimeout: number;
//const transition = Transition.bind(null, searchContainer.parentElement, 150);
const transition = TransitionSlider(searchContainer.parentElement, 'zoom-fade', 150, (id) => {
if(hideNewBtnMenuTimeout) clearTimeout(hideNewBtnMenuTimeout);
if(id === 0) {
this.inputSearch.onClearClick();
hideNewBtnMenuTimeout = window.setTimeout(() => {
hideNewBtnMenuTimeout = 0;
this.newBtnMenu.classList.remove('is-hidden');
}, 150);
}
});
transition(0);
const onFocus = () => {
this.toolsBtn.classList.remove('active');
this.backBtn.classList.add('active');
this.newBtnMenu.classList.add('is-hidden');
transition(1);
};
this.inputSearch.input.addEventListener('focus', onFocus);
onFocus();
this.backBtn.addEventListener('click', (e) => {
this.toolsBtn.classList.add('active');
this.backBtn.classList.remove('active');
transition(0);
});
const clearRecentSearchBtn = document.createElement('button');
clearRecentSearchBtn.classList.add('btn-icon', 'tgico-close');
this.searchGroups.recent.nameEl.append(clearRecentSearchBtn);
clearRecentSearchBtn.addEventListener('click', () => {
this.searchGroups.recent.clear();
appStateManager.pushToState('recentSearch', []);
});
}
}

29
src/components/sidebarRight/tabs/sharedMedia.ts

@ -1,12 +1,12 @@ @@ -1,12 +1,12 @@
import appImManager from "../../../lib/appManagers/appImManager";
import appMessagesManager, { MyInputMessagesFilter } from "../../../lib/appManagers/appMessagesManager";
import appMessagesManager, { MyInputMessagesFilter, MyMessage } from "../../../lib/appManagers/appMessagesManager";
import appPeersManager from "../../../lib/appManagers/appPeersManager";
import appProfileManager from "../../../lib/appManagers/appProfileManager";
import appUsersManager from "../../../lib/appManagers/appUsersManager";
import { logger } from "../../../lib/logger";
import { RichTextProcessor } from "../../../lib/richtextprocessor";
import rootScope from "../../../lib/rootScope";
import AppSearchSuper from "../../appSearchSuper.";
import AppSearchSuper, { SearchSuperType } from "../../appSearchSuper.";
import AvatarElement from "../../avatar";
import Scrollable from "../../scrollable";
import { SliderTab } from "../../slider";
@ -48,7 +48,7 @@ export default class AppSharedMediaTab implements SliderTab { @@ -48,7 +48,7 @@ export default class AppSharedMediaTab implements SliderTab {
public historiesStorage: {
[peerId: number]: Partial<{
[type in MyInputMessagesFilter]: number[]
[type in SearchSuperType]: {mid: number, peerId: number}[]
}>
} = {};
@ -118,13 +118,13 @@ export default class AppSharedMediaTab implements SliderTab { @@ -118,13 +118,13 @@ export default class AppSharedMediaTab implements SliderTab {
name: 'Media'
}, {
inputFilter: 'inputMessagesFilterDocument',
name: 'Docs'
name: 'Files'
}, {
inputFilter: 'inputMessagesFilterUrl',
name: 'Links'
}, {
inputFilter: 'inputMessagesFilterMusic',
name: 'Audio'
name: 'Music'
}], this.scroll);
this.profileContentEl.append(this.searchSuper.container);
@ -155,10 +155,10 @@ export default class AppSharedMediaTab implements SliderTab { @@ -155,10 +155,10 @@ export default class AppSharedMediaTab implements SliderTab {
mids = mids.slice().reverse(); // ! because it will be ascend sorted array
for(const type of this.searchSuper.types) {
const inputFilter = type.inputFilter;
const filtered = this.searchSuper.filterMessagesByType(mids, inputFilter);
const filtered = this.searchSuper.filterMessagesByType(mids.map(mid => appMessagesManager.getMessageByPeer(peerId, mid)), inputFilter);
if(filtered.length) {
if(this.historiesStorage[peerId][inputFilter]) {
this.historiesStorage[peerId][inputFilter].unshift(...mids);
this.historiesStorage[peerId][inputFilter].unshift(...filtered.map(message => ({mid: message.mid, peerId: message.peerId})));
}
if(this.peerId == peerId && this.searchSuper.usedFromHistory[inputFilter] !== -1) {
@ -183,18 +183,14 @@ export default class AppSharedMediaTab implements SliderTab { @@ -183,18 +183,14 @@ export default class AppSharedMediaTab implements SliderTab {
if(!this.historiesStorage[peerId][inputFilter]) continue;
const history = this.historiesStorage[peerId][inputFilter];
const idx = history.findIndex(m => m == mid);
const idx = history.findIndex(m => m.mid === mid);
if(idx !== -1) {
history.splice(idx, 1);
if(this.peerId == peerId) {
const container = this.searchSuper.tabs[inputFilter];
const div = container.querySelector(`div[data-mid="${mid}"]`);
const div = container.querySelector(`div[data-mid="${mid}"][data-peer-id="${peerId}"]`);
if(div) {
if(inputFilter == 'inputMessagesFilterPhotoVideo') {
delete this.searchSuper.mediaDivsByIds[mid];
}
div.remove();
}
@ -220,6 +216,7 @@ export default class AppSharedMediaTab implements SliderTab { @@ -220,6 +216,7 @@ export default class AppSharedMediaTab implements SliderTab {
this.profileElements.notificationsStatus.innerText = 'Enabled';
this.searchSuper.cleanupHTML();
this.searchSuper.selectTab(0, false);
}
public setLoadMutex(promise: Promise<any>) {
@ -236,7 +233,11 @@ export default class AppSharedMediaTab implements SliderTab { @@ -236,7 +233,11 @@ export default class AppSharedMediaTab implements SliderTab {
this.peerId = peerId;
this.threadId = threadId;
this.searchSuper.setQuery(peerId, '', threadId, this.historiesStorage[peerId] ?? (this.historiesStorage[peerId] = {}));
this.searchSuper.setQuery({
peerId,
//threadId,
historyStorage: this.historiesStorage[peerId] ?? (this.historiesStorage[peerId] = {})
});
this.cleaned = true;
}

26
src/components/wrappers.ts

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { getEmojiToneIndex } from '../emoji';
import { readBlobAsText } from '../helpers/blob';
import { deferredPromise } from '../helpers/cancellablePromise';
import { months } from '../helpers/date';
import { formatDateAccordingToToday, months } from '../helpers/date';
import mediaSizes from '../helpers/mediaSizes';
import { formatBytes } from '../helpers/number';
import { isAppleMobile, isSafari } from '../helpers/userAgent';
@ -28,6 +28,7 @@ import { nextRandomInt } from '../helpers/random'; @@ -28,6 +28,7 @@ import { nextRandomInt } from '../helpers/random';
import RichTextProcessor from '../lib/richtextprocessor';
import appImManager from '../lib/appManagers/appImManager';
import Chat from './chat/chat';
import { SearchSuperContext } from './appSearchSuper.';
const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB
@ -343,10 +344,13 @@ export const formatDate = (timestamp: number, monthShort = false, withYear = tru @@ -343,10 +344,13 @@ export const formatDate = (timestamp: number, monthShort = false, withYear = tru
return str + ' at ' + date.getHours() + ':' + ('0' + date.getMinutes()).slice(-2);
};
export function wrapDocument({message, withTime, fontWeight}: {
export function wrapDocument({message, withTime, fontWeight, voiceAsMusic, showSender, searchContext}: {
message: any,
withTime?: boolean,
fontWeight?: number
fontWeight?: number,
voiceAsMusic?: boolean,
showSender?: boolean,
searchContext?: SearchSuperContext
}): HTMLElement {
if(!fontWeight) fontWeight = 500;
@ -359,6 +363,11 @@ export function wrapDocument({message, withTime, fontWeight}: { @@ -359,6 +363,11 @@ export function wrapDocument({message, withTime, fontWeight}: {
audioElement.setAttribute('peer-id', '' + message.peerId);
audioElement.withTime = withTime;
audioElement.message = message;
if(voiceAsMusic) audioElement.voiceAsMusic = voiceAsMusic;
if(searchContext) audioElement.searchContext = searchContext;
if(showSender) audioElement.showSender = showSender;
audioElement.dataset.fontWeight = '' + fontWeight;
audioElement.render();
return audioElement;
@ -403,10 +412,19 @@ export function wrapDocument({message, withTime, fontWeight}: { @@ -403,10 +412,19 @@ export function wrapDocument({message, withTime, fontWeight}: {
if(withTime) {
size += ' · ' + formatDate(doc.date);
}
if(showSender) {
size += ' · ' + appMessagesManager.getSenderToPeerText(message);
}
let titleAdditionHTML = '';
if(showSender) {
titleAdditionHTML = `<div class="sent-time">${formatDateAccordingToToday(new Date(message.date * 1000))}</div>`;
}
docDiv.innerHTML = `
${!uploading ? `<div class="document-download"><div class="tgico-download"></div></div>` : ''}
<div class="document-name"><middle-ellipsis-element data-font-weight="${fontWeight}">${fileName}</middle-ellipsis-element></div>
<div class="document-name"><middle-ellipsis-element data-font-weight="${fontWeight}">${fileName}</middle-ellipsis-element>${titleAdditionHTML}</div>
<div class="document-size">${size}</div>
`;

49
src/lib/appManagers/appMessagesManager.ts

@ -1887,7 +1887,12 @@ export class AppMessagesManager { @@ -1887,7 +1887,12 @@ export class AppMessagesManager {
if(p.promise) return p.promise;
else if(p.maxId) return Promise.resolve(p);
return p.promise = this.getSearch(peerId, '', {_: 'inputMessagesFilterPinned'}, 0, 1).then(result => {
return p.promise = this.getSearchNew({
peerId,
inputFilter: {_: 'inputMessagesFilterPinned'},
maxId: 0,
limit: 1
}).then(result => {
p.count = result.count;
p.maxId = result.history[0]?.mid;
return p;
@ -2380,8 +2385,7 @@ export class AppMessagesManager { @@ -2380,8 +2385,7 @@ export class AppMessagesManager {
let messageWrapped = '';
if(text) {
// * 80 for chatlist in landscape orientation
text = limitSymbols(text, 75, 80);
text = limitSymbols(text, 100);
const entities = RichTextProcessor.parseEntities(text.replace(/\n/g, ' '));
@ -2396,6 +2400,21 @@ export class AppMessagesManager { @@ -2396,6 +2400,21 @@ export class AppMessagesManager {
return messageText + messageWrapped;
}
public getSenderToPeerText(message: MyMessage) {
let senderTitle = '', peerTitle: string;
senderTitle = message.pFlags.out ? 'You' : appPeersManager.getPeerTitle(message.fromId, false, false);
peerTitle = appPeersManager.isAnyGroup(message.peerId) || (message.pFlags.out && message.peerId !== rootScope.myId) ?
appPeersManager.getPeerTitle(message.peerId, false, false) :
'';
if(peerTitle) {
senderTitle += ' ➝ ' + peerTitle;
}
return senderTitle;
}
public wrapMessageActionText(message: any) {
const action = message.action as MessageAction;
@ -2868,24 +2887,25 @@ export class AppMessagesManager { @@ -2868,24 +2887,25 @@ export class AppMessagesManager {
});
}
public getSearchNew({peerId, query, inputFilter, maxId, limit, offsetRate, backLimit, threadId}: {
peerId: number,
maxId: number,
public getSearchNew({peerId, query, inputFilter, maxId, limit, nextRate, backLimit, threadId, folderId}: {
peerId?: number,
maxId?: number,
limit?: number,
offsetRate?: number,
nextRate?: number,
backLimit?: number,
threadId?: number,
folderId?: number,
query?: string,
inputFilter?: {
_: MyInputMessagesFilter
},
}) {
return this.getSearch(peerId, query, inputFilter, maxId, limit, offsetRate, backLimit, threadId);
return this.getSearch(peerId, query, inputFilter, maxId, limit, nextRate, backLimit, threadId, folderId);
}
public getSearch(peerId = 0, query: string = '', inputFilter: {
_: MyInputMessagesFilter
} = {_: 'inputMessagesFilterEmpty'}, maxId: number, limit = 20, offsetRate = 0, backLimit = 0, threadId = 0): Promise<{
} = {_: 'inputMessagesFilterEmpty'}, maxId: number, limit = 20, nextRate = 0, backLimit = 0, threadId?: number, folderId?: number): Promise<{
count: number,
next_rate: number,
offset_id_offset: number,
@ -3052,7 +3072,7 @@ export class AppMessagesManager { @@ -3052,7 +3072,7 @@ export class AppMessagesManager {
}
let apiPromise: Promise<any>;
if(peerId || !query) {
if(peerId && !nextRate && folderId === undefined/* || !query */) {
apiPromise = apiManager.invokeApi('messages.search', {
peer: appPeersManager.getInputPeerById(peerId),
q: query || '',
@ -3087,10 +3107,11 @@ export class AppMessagesManager { @@ -3087,10 +3107,11 @@ export class AppMessagesManager {
filter: inputFilter as any as MessagesFilter,
min_date: 0,
max_date: 0,
offset_rate: offsetRate,
offset_rate: nextRate,
offset_peer: appPeersManager.getInputPeerById(offsetPeerId),
offset_id: offsetId,
limit,
folder_id: folderId
}, {
//timeout: APITIMEOUT,
noErrorBox: true
@ -3110,7 +3131,7 @@ export class AppMessagesManager { @@ -3110,7 +3131,7 @@ export class AppMessagesManager {
storage.count = searchResult.count;
}
this.log('messages.search result:', inputFilter, searchResult);
this.log('getSearch result:', inputFilter, searchResult);
const foundCount: number = searchResult.count || (foundMsgs.length + searchResult.messages.length);
@ -4298,7 +4319,7 @@ export class AppMessagesManager { @@ -4298,7 +4319,7 @@ export class AppMessagesManager {
}
} 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;
@ -4325,7 +4346,7 @@ export class AppMessagesManager { @@ -4325,7 +4346,7 @@ export class AppMessagesManager {
AppStorage.set({max_seen_msg: maxId});
apiManager.invokeApi('messages.receivedMessages', {
max_id: maxId
max_id: this.getLocalMessageId(maxId)
});
}

2
src/lib/appManagers/appPeersManager.ts

@ -79,7 +79,7 @@ export class AppPeersManager { @@ -79,7 +79,7 @@ export class AppPeersManager {
return plainText ? title : RichTextProcessor.wrapEmojiText(title);
}
public getOutputPeer(peerId: number): Peer {
if(peerId > 0) {
return {_: 'peerUser', user_id: peerId};

8
src/scss/partials/_chatPinned.scss

@ -295,8 +295,12 @@ @@ -295,8 +295,12 @@
}
}
&.is-floating .pinned-message-subtitle {
max-width: 280px;
&.is-floating {
.chat:not(.type-discussion) & {
.pinned-container-wrapper {
padding-right: 3rem;
}
}
}
&-content {

81
src/scss/partials/_rightSidebar.scss

@ -230,20 +230,27 @@ @@ -230,20 +230,27 @@
}
}
#shared-media-container {
.search-super {
top: 100%;
min-height: calc((var(--vh, 1vh) * 100) - 100% - 56px);
&.sliding {
max-height: calc((var(--vh, 1vh) * 100) - 100% - 56px);
}
}
}
.search-super {
width: 100%;
max-width: 100%;
//overflow: hidden;
position: absolute;
top: 100%;
//min-height: 100vh; /* Fallback for browsers that do not support Custom Properties */
//min-height: calc(var(--vh, 1vh) * 100);
min-height: calc((var(--vh, 1vh) * 100) - 100% - 56px);
min-height: 100%;
display: flex;
flex-direction: column;
&.sliding {
max-height: calc((var(--vh, 1vh) * 100) - 100% - 56px);
max-height: 100%;
}
&-tabs {
@ -304,19 +311,11 @@ @@ -304,19 +311,11 @@
}
}
&-content-media {
width: 100%;
padding: 7.5px;
display: grid;
grid-template-columns: repeat(3,1fr);
grid-auto-rows: 1fr;
grid-gap: 3.5px;
@include respond-to(handhelds) {
padding: 7.5px 7.5px 7.5px 6.5px;
}
&-month:first-of-type &-month-name {
display: none;
}
&-content-media {
.video-time {
position: absolute;
left: 5px;
@ -353,7 +352,23 @@ @@ -353,7 +352,23 @@
} */
}
&-content-docs {
&-content-media &-month {
&-items {
width: 100%;
padding: 7.5px;
display: grid;
grid-template-columns: repeat(3,1fr);
grid-auto-rows: 1fr;
grid-gap: 3.5px;
@include respond-to(handhelds) {
padding: 7.5px 7.5px 7.5px 6.5px;
}
}
}
&-content-files {
padding: 7px 20px;
.document {
@ -385,7 +400,7 @@ @@ -385,7 +400,7 @@
&-content-links {
padding: 0 30px 15px 15px;
> div {
.search-super-item {
display: flex;
flex-direction: column;
margin-top: 20px;
@ -438,16 +453,12 @@ @@ -438,16 +453,12 @@
}
}
&-content-audio {
&-content-music, &-content-voice {
padding: 20px 15px 15px 20px;
> div {
min-height: 60px;
}
.preloader-container {
.preloader-circular {
background-color: rgba(0, 0, 0, 0.35);
background-color: rgba(0, 0, 0, .35);
}
@include respond-to(handhelds) {
@ -497,6 +508,24 @@ @@ -497,6 +508,24 @@
}
}
#search-container {
.search-super-content-music {
.audio:not(.audio-show-progress) .audio-time {
display: none;
}
}
.document-name, .audio-title, .title {
display: flex;
justify-content: space-between;
}
.sent-time {
flex: 0 0 auto;
margin-left: .5rem;
}
}
#stickers-container {
.sticker-sets {
display: flex;

2
src/scss/partials/_slider.scss

@ -42,6 +42,7 @@ $slider-time: .25s; @@ -42,6 +42,7 @@ $slider-time: .25s;
position: relative;
display: inline-flex;
align-items: center;
overflow: visible;
}
&.active {
@ -121,6 +122,7 @@ $slider-time: .25s; @@ -121,6 +122,7 @@ $slider-time: .25s;
position: relative;
display: inline-flex;
align-items: center;
overflow: visible;
}
&.active {

Loading…
Cancel
Save