diff --git a/src/components/appMediaPlaybackController.ts b/src/components/appMediaPlaybackController.ts index 62b700ed..55b1b4a4 100644 --- a/src/components/appMediaPlaybackController.ts +++ b/src/components/appMediaPlaybackController.ts @@ -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; } diff --git a/src/components/appMediaViewer.ts b/src/components/appMediaViewer.ts index c70ff8b4..0760ad24 100644 --- a/src/components/appMediaViewer.ts +++ b/src/components/appMediaViewer.ts @@ -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 { 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 } */ 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 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 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 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 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 } } - 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); diff --git a/src/components/appSearch.ts b/src/components/appSearch.ts index 321457a4..5610eaac 100644 --- a/src/components/appSearch.ts +++ b/src/components/appSearch.ts @@ -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 { } } -/** - * * 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 = null; private searchTimeout: number = 0; @@ -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 { const maxId = this.minMsgId || 0; - if(!this.peerId && !maxId && !this.loadedContacts) { - const renderedPeerIds: Set = 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, '$1'); - 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 = '' + username + ''; - } - }); - - group.toggle(); - }; - - const onLoad = (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 { 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); } diff --git a/src/components/appSearchSuper..ts b/src/components/appSearchSuper..ts index 64379efa..6cf0950d 100644 --- a/src/components/appSearchSuper..ts +++ b/src/components/appSearchSuper..ts @@ -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 { 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 = Promise.resolve(); - private loadPromises: Partial<{[type in MyInputMessagesFilter]: Promise}> = {}; - private loaded: Partial<{[type in MyInputMessagesFilter]: boolean}> = {}; + private nextRates: Partial<{[type in SearchSuperType]: number}> = {}; + private loadPromises: Partial<{[type in SearchSuperType]: Promise}> = {}; + 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; + + 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 { }; //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 { }); 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 { 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 { 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 { } 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 { 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 { 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[] = []; - 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 { 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 { 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 { }); 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 { 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 { } 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 { 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 { title = RichTextProcessor.wrapPlainText(webpage.display_url.split('/', 1)[0]); } - /* if(webpage.description?.includes('Еще в начале')) { - console.error('FROM THE START', webpage); - } */ - + let sender = this.asChatList ? `
${appMessagesManager.getSenderToPeerText(message)}
` : ''; + + let titleAdditionHTML = ''; + if(this.asChatList) { + titleAdditionHTML = `
${formatDateAccordingToToday(new Date(message.date * 1000))}
`; + } + div.append(previewDiv); div.insertAdjacentHTML('beforeend', ` -
${title} +
${title}${titleAdditionHTML}
${subtitle}
${url}
+ ${sender} `); if(div.innerText.trim().length) { - elemsToAppend.push(div); + elemsToAppend.push({element: div, message}); } } @@ -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 { 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 { } } } + + private loadChats() { + const renderedPeerIds: Set = 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, '$1'); + 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 = '' + username + ''; + } + }); + + group.toggle(); + }; + + const onLoad = (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 { const historyStorage = this.historyStorage ?? (this.historyStorage = {}); - const promises = typesToLoad.map(type => { + const middleware = this.getMiddleware(); + + const promises: Promise[] = 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 { 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 { 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 { 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 { 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 { } } }); + + this.monthContainers = {}; if(testScroll) { for(let i = 0; i < 1500; ++i) { @@ -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(); } diff --git a/src/components/audio.ts b/src/components/audio.ts index b8676d78..01495632 100644 --- a/src/components/audio.ts +++ b/src/components/audio.ts @@ -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) { 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 = `
${formatDateAccordingToToday(new Date(message.date * 1000))}
`; + } const html = `
-
${title}
+
${title}${titleAdditionHTML}
${subtitle}
`; @@ -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 { 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(); diff --git a/src/components/avatar.ts b/src/components/avatar.ts index 67000434..56b744cf 100644 --- a/src/components/avatar.ts +++ b/src/components/avatar.ts @@ -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 { } 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; } diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index dbcc6725..bcabdfbd 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -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 { 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 { 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 { 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; diff --git a/src/components/chat/pinnedMessage.ts b/src/components/chat/pinnedMessage.ts index ab7aab7a..938dec57 100644 --- a/src/components/chat/pinnedMessage.ts +++ b/src/components/chat/pinnedMessage.ts @@ -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; diff --git a/src/components/horizontalMenu.ts b/src/components/horizontalMenu.ts index 3567d326..4f0b97fc 100644 --- a/src/components/horizontalMenu.ts +++ b/src/components/horizontalMenu.ts @@ -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? 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? // 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; diff --git a/src/components/inputSearch.ts b/src/components/inputSearch.ts index eb3b60f1..364c0aae 100644 --- a/src/components/inputSearch.ts +++ b/src/components/inputSearch.ts @@ -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(() => { diff --git a/src/components/sidebarLeft/index.ts b/src/components/sidebarLeft/index.ts index 7f2b68e1..e5118df9 100644 --- a/src/components/sidebarLeft/index.ts +++ b/src/components/sidebarLeft/index.ts @@ -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"; 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(); 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 { 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 { //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 { 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 { 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 { 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', []); + }); } } diff --git a/src/components/sidebarRight/tabs/sharedMedia.ts b/src/components/sidebarRight/tabs/sharedMedia.ts index 0e360b35..821f3775 100644 --- a/src/components/sidebarRight/tabs/sharedMedia.ts +++ b/src/components/sidebarRight/tabs/sharedMedia.ts @@ -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 { 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 { 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 { 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 { 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 { this.profileElements.notificationsStatus.innerText = 'Enabled'; this.searchSuper.cleanupHTML(); + this.searchSuper.selectTab(0, false); } public setLoadMutex(promise: Promise) { @@ -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; } diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index 2bac5d7c..abbdcea1 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -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'; 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 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}: { 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}: { if(withTime) { size += ' · ' + formatDate(doc.date); } + + if(showSender) { + size += ' · ' + appMessagesManager.getSenderToPeerText(message); + } + + let titleAdditionHTML = ''; + if(showSender) { + titleAdditionHTML = `
${formatDateAccordingToToday(new Date(message.date * 1000))}
`; + } docDiv.innerHTML = ` ${!uploading ? `
` : ''} -
${fileName}
+
${fileName}${titleAdditionHTML}
${size}
`; diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index d3530c69..8f795ffe 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -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 { 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 { 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 { }); } - 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 { } let apiPromise: Promise; - 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 { 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 { 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 { } } 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 { AppStorage.set({max_seen_msg: maxId}); apiManager.invokeApi('messages.receivedMessages', { - max_id: maxId + max_id: this.getLocalMessageId(maxId) }); } diff --git a/src/lib/appManagers/appPeersManager.ts b/src/lib/appManagers/appPeersManager.ts index 59c293e8..2dfb22dc 100644 --- a/src/lib/appManagers/appPeersManager.ts +++ b/src/lib/appManagers/appPeersManager.ts @@ -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}; diff --git a/src/scss/partials/_chatPinned.scss b/src/scss/partials/_chatPinned.scss index 04f1e149..cedd749d 100644 --- a/src/scss/partials/_chatPinned.scss +++ b/src/scss/partials/_chatPinned.scss @@ -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 { diff --git a/src/scss/partials/_rightSidebar.scss b/src/scss/partials/_rightSidebar.scss index 08052778..19e96cd1 100644 --- a/src/scss/partials/_rightSidebar.scss +++ b/src/scss/partials/_rightSidebar.scss @@ -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 @@ } } - &-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 @@ } */ } - &-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 @@ &-content-links { padding: 0 30px 15px 15px; - > div { + .search-super-item { display: flex; flex-direction: column; margin-top: 20px; @@ -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 @@ } } +#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; diff --git a/src/scss/partials/_slider.scss b/src/scss/partials/_slider.scss index 26c71558..1c51b821 100644 --- a/src/scss/partials/_slider.scss +++ b/src/scss/partials/_slider.scss @@ -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; position: relative; display: inline-flex; align-items: center; + overflow: visible; } &.active {