diff --git a/src/components/appSearch.ts b/src/components/appSearch.ts new file mode 100644 index 00000000..33777d42 --- /dev/null +++ b/src/components/appSearch.ts @@ -0,0 +1,236 @@ +import appDialogsManager from "../lib/appManagers/appDialogsManager"; +import Scrollable from "./scrollable"; +import appMessagesIDsManager from "../lib/appManagers/appMessagesIDsManager"; +import appUsersManager from "../lib/appManagers/appUsersManager"; +import appPeersManager from '../lib/appManagers/appPeersManager'; +import appMessagesManager from "../lib/appManagers/appMessagesManager"; +import { numberWithCommas, escapeRegExp } from "../lib/utils"; +import { formatPhoneNumber } from "./misc"; + +export class SearchGroup { + container: HTMLDivElement; + nameEl: HTMLDivElement; + list: HTMLUListElement; + + constructor(public name: string, public type: string) { + this.list = document.createElement('ul'); + this.container = document.createElement('div'); + this.nameEl = document.createElement('div'); + this.nameEl.classList.add('search-group__name'); + this.nameEl.innerText = name; + + this.container.classList.add('search-group'); + this.container.append(this.nameEl, this.list); + this.container.style.display = 'none'; + + appDialogsManager.setListClickListener(this.list); + } + + clear() { + this.container.style.display = 'none'; + this.list.innerHTML = ''; + } + + setActive() { + this.container.style.display = ''; + } +} + +export default class AppSearch { + private minMsgID = 0; + private loadedCount = 0; + private foundCount = 0; + private offsetRate = 0; + + private searchPromise: Promise = null; + private searchTimeout: number = 0; + + private query = ''; + + private listsContainer: HTMLDivElement = null; + + private peerID = 0; // 0 - means global + + private scrollable: Scrollable; + + constructor(public container: HTMLDivElement, public searchInput: HTMLInputElement, public searchGroups: {[group: string]: SearchGroup}) { + this.scrollable = new Scrollable(this.container); + this.listsContainer = this.scrollable.container; + for(let i in this.searchGroups) { + this.listsContainer.append(this.searchGroups[i].container); + } + + this.searchInput.addEventListener('input', (e) => { + let value = this.searchInput.value; + if(!value.trim()) { + //this.peerID = 0; + return; + } + + this.query = value; + this.reset(false); + this.searchMore(); + }); + + this.scrollable.onScrolledBottom = () => { + if(!this.query.trim()) return; + + if(!this.searchTimeout) { + this.searchTimeout = setTimeout(() => { + this.searchMore(); + this.searchTimeout = 0; + }, 0); + } + }; + } + + public reset(all = true) { + if(all) { + this.searchInput.value = ''; + this.query = ''; + this.peerID = 0; + } + + this.minMsgID = 0; + this.loadedCount = 0; + this.foundCount = 0; + this.offsetRate = 0; + + for(let i in this.searchGroups) { + this.searchGroups[i].clear(); + } + + this.searchPromise = null; + } + + public beginSearch(peerID?: number) { + if(peerID) { + this.peerID = peerID; + } + + this.searchInput.focus(); + } + + private searchMore() { + if(this.searchPromise) return this.searchPromise; + + let query = this.query; + + if(!query.trim()) return; + + if(this.loadedCount != 0 && this.loadedCount >= this.foundCount) { + return Promise.resolve(); + } + + let maxID = appMessagesIDsManager.getMessageIDInfo(this.minMsgID)[0]; + + if(!this.peerID && !maxID) { + appUsersManager.searchContacts(query, 20).then((contacts: any) => { + if(this.searchInput.value != query) { + return; + } + + ///////this.log('input search contacts result:', contacts); + + let setResults = (results: any, group: SearchGroup, showMembersCount = false) => { + results.forEach((inputPeer: any) => { + let peerID = appPeersManager.getPeerID(inputPeer); + let peer = appPeersManager.getPeer(peerID); + let originalDialog = appMessagesManager.getDialogByPeerID(peerID)[0]; + + //////////this.log('contacts peer', peer); + + if(!originalDialog) { + /////////this.log('no original dialog by peerID:', peerID); + + originalDialog = { + peerID: peerID, + pFlags: {}, + peer: peer + }; + } + + let {dialog, dom} = appDialogsManager.addDialog(originalDialog, group.list, false); + + if(showMembersCount && (peer.participants_count || peer.participants)) { + let regExp = new RegExp(`(${escapeRegExp(query)})`, 'gi'); + dom.titleSpan.innerHTML = dom.titleSpan.innerHTML.replace(regExp, '$1'); + + let isChannel = appPeersManager.isChannel(peerID) && !appPeersManager.isMegagroup(peerID); + let participants_count = peer.participants_count || peer.participants.participants.length; + let subtitle = numberWithCommas(participants_count) + ' ' + (isChannel ? 'subscribers' : 'members'); + dom.lastMessageSpan.innerText = subtitle; + } else { + let username = appPeersManager.getPeerUsername(peerID); + if(!username) { + let user = appUsersManager.getUser(peerID); + if(user && user.phone) { + username = '+' + formatPhoneNumber(user.phone).formatted; + } + } else { + username = '@' + username; + } + + dom.lastMessageSpan.innerHTML = '' + username + ''; + } + }); + + if(results.length) { + group.setActive(); + } + }; + + setResults(contacts.my_results, this.searchGroups.contacts, true); + setResults(contacts.results, this.searchGroups.globalContacts); + }); + } + + return this.searchPromise = appMessagesManager.getSearch(this.peerID, query, null, maxID, 20, this.offsetRate).then(res => { + this.searchPromise = null; + + if(this.searchInput.value != query) { + return; + } + + /////////this.log('input search result:', this.peerID, query, null, maxID, 20, res); + + let {count, history, next_rate} = res; + + if(history[0] == this.minMsgID) { + history.shift(); + } + + let searchGroup = this.searchGroups['messages']; + searchGroup.setActive(); + + history.forEach((msgID: number) => { + let message = appMessagesManager.getMessage(msgID); + let originalDialog = appMessagesManager.getDialogByPeerID(message.peerID)[0]; + + if(!originalDialog) { + ////////this.log('no original dialog by message:', message); + + originalDialog = { + peerID: message.peerID, + pFlags: {}, + peer: message.to_id + }; + } + + let {dialog, dom} = appDialogsManager.addDialog(originalDialog, searchGroup.list, false); + appDialogsManager.setLastMessage(dialog, message, dom, query); + }); + + this.minMsgID = history[history.length - 1]; + this.offsetRate = next_rate; + this.loadedCount += history.length; + + if(!this.foundCount) { + this.foundCount = count; + } + }).catch(err => { + console.error('search error', err); + this.searchPromise = null; + }); + } +} diff --git a/src/components/emoticonsDropdown.ts b/src/components/emoticonsDropdown.ts index 7e5e5c5b..52333d6e 100644 --- a/src/components/emoticonsDropdown.ts +++ b/src/components/emoticonsDropdown.ts @@ -34,7 +34,9 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, lottieLoader.checkAnimations(false, EMOTICONSSTICKERGROUP); lazyLoadQueue.check(); // for stickers }); - (tabs.children[0] as HTMLLIElement).click(); // set media + + (tabs.firstElementChild.children[0] as HTMLLIElement).click(); // set emoji tab + (tabs.lastElementChild as HTMLSpanElement).style.cssText = 'width: 44.1719px; transform: translateX(88.5781px);'; // мы снова встретились)))))) let emoticonsMenuOnClick = (menu: HTMLUListElement, heights: number[], scroll: Scrollable, menuScroll?: Scrollable) => { menu.addEventListener('click', function(e) { @@ -151,7 +153,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, let heights: number[] = [0]; let prevCategoryIndex = 1; - let menu = contentEmojiDiv.nextElementSibling as HTMLUListElement; + let menu = contentEmojiDiv.nextElementSibling.firstElementChild as HTMLUListElement; let emojiScroll = new Scrollable(contentEmojiDiv, 'y', 500, 'EMOJI', null); emojiScroll.container.addEventListener('scroll', (e) => { prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, emojiScroll.container); @@ -205,7 +207,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, //let stickersDiv = contentStickersDiv.querySelector('.os-content') as HTMLDivElement; let menuWrapper = contentStickersDiv.nextElementSibling as HTMLDivElement; - let menu = menuWrapper.firstElementChild as HTMLUListElement; + let menu = menuWrapper.firstElementChild.firstElementChild as HTMLUListElement; let menuScroll = new Scrollable(menuWrapper, 'x'); diff --git a/src/components/misc.ts b/src/components/misc.ts index 3b1a0e08..2f865cb9 100644 --- a/src/components/misc.ts +++ b/src/components/misc.ts @@ -1,4 +1,4 @@ -import { whichChild, findUpTag, cancelEvent } from "../lib/utils"; +import { whichChild, findUpTag } from "../lib/utils"; let rippleClickID = 0; export function ripple(elem: HTMLElement, callback: (id: number) => Promise = () => Promise.resolve(), onEnd: (id: number) => void = null) { @@ -141,13 +141,13 @@ export function putPreloader(elem: Element, returnDiv = false) { elem.innerHTML += html; } -export function horizontalMenu(tabs: HTMLUListElement, content: HTMLDivElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 300) { +export function horizontalMenu(tabs: HTMLElement, content: HTMLDivElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 300) { let hideTimeout: number = 0; let prevTabContent: HTMLDivElement = null; let prevId = -1; let children = Array.from(content.children); - let tabsChildren = tabs ? Array.from(tabs.children) : []; + let tabsChildren = tabs ? Array.from(tabs.firstElementChild.children) : []; let activeInSlide: Set = new Set(); let selectTab = (id: number) => { @@ -227,6 +227,11 @@ export function horizontalMenu(tabs: HTMLUListElement, content: HTMLDivElement, }; if(tabs) { + let activeStripe = document.createElement('span'); + activeStripe.classList.add('menu-horizontal__stripe'); + + tabs.append(activeStripe); + tabs.addEventListener('click', function(e) { let target = e.target as HTMLLIElement; @@ -242,7 +247,6 @@ export function horizontalMenu(tabs: HTMLUListElement, content: HTMLDivElement, let tabContent = content.children[id] as HTMLDivElement; if(activeInSlide.size >= 2 && !activeInSlide.has(tabContent)) { - cancelEvent(e); return false; } @@ -253,7 +257,13 @@ export function horizontalMenu(tabs: HTMLUListElement, content: HTMLDivElement, let prev = tabs.querySelector('li.active') as HTMLLIElement; prev && prev.classList.remove('active'); - + + let tabsRect = tabs.getBoundingClientRect(); + let textRect = target.firstElementChild.getBoundingClientRect(); + activeStripe.style.cssText = `width: ${textRect.width + (2 * 2)}px; transform: translateX(${textRect.left - tabsRect.left}px);`; + //activeStripe.style.transform = `scaleX(${textRect.width}) translateX(${(textRect.left - tabsRect.left) / textRect.width + 0.5}px)`; + console.log('tabs click:', tabsRect, textRect); + target.classList.add('active'); selectTab(id); }); diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index bdf6d037..66a6b640 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -1,4 +1,4 @@ -import { langPack, findUpClassName, $rootScope } from "../utils"; +import { langPack, findUpClassName, $rootScope, escapeRegExp } from "../utils"; import appImManager, { AppImManager } from "./appImManager"; import appPeersManager from './appPeersManager'; import appMessagesManager, { AppMessagesManager } from "./appMessagesManager"; @@ -420,7 +420,7 @@ export class AppDialogsManager { chatsHidden.down = inBottom; } - public setLastMessage(dialog: any, lastMessage?: any, dom?: DialogDom) { + public setLastMessage(dialog: any, lastMessage?: any, dom?: DialogDom, highlightWord?: string) { if(!lastMessage) { lastMessage = appMessagesManager.getMessage(dialog.top_message); } @@ -497,12 +497,58 @@ export class AppDialogsManager { } if(lastMessage.action) { + let action = lastMessage.action; + + console.log('lastMessage action:', action); + + let suffix = ''; + let _ = action._; + if(_ == "messageActionPhoneCall") { + _ += '.' + action.type; + + let duration = action.duration; + if(duration) { + let d = []; + + d.push(duration % 60 + ' s'); + if(duration > 60) d.push((duration / 60 | 0) + ' min'); + if(duration > 3600) d.push((duration / 3600 | 0) + ' h'); + suffix = ' (' + d.reverse().join(' ') + ')'; + } + } + // @ts-ignore - lastMessageText = langPack[lastMessage.action._]; + lastMessageText = '' + langPack[_] + suffix + ''; } - - dom.lastMessageSpan.innerHTML = lastMessageText + - (lastMessage.message ? RichTextProcessor.wrapRichText(lastMessage.message.replace(/\n/g, ' '), {noLinebreakers: true}) : ''); + + let messageText = lastMessage.message; + let messageWrapped = ''; + if(messageText) { + let entities = RichTextProcessor.parseEntities(messageText, {noLinebreakers: true}); + if(highlightWord) { + let regExp = new RegExp(escapeRegExp(highlightWord), 'gi'); + let match: any; + + if(!entities) entities = []; + let found = false; + while((match = regExp.exec(messageText)) !== null) { + entities.push({_: 'messageEntityHighlight', length: highlightWord.length, offset: match.index}); + found = true; + } + + if(found) { + entities.sort((a: any, b: any) => a.offset - b.offset); + } + } + + messageWrapped = RichTextProcessor.wrapRichText(messageText.replace(/\n/g, ' '), { + noLinebreakers: true, + entities: entities, + noTextFormat: true + }); + } + + dom.lastMessageSpan.innerHTML = lastMessageText + messageWrapped; /* if(lastMessage.from_id == auth.id) { // You: */ if(peer._ != 'peerUser' && peerID != -lastMessage.from_id) { diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 39aba081..230f0bde 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -26,6 +26,8 @@ import LazyLoadQueue from '../../components/lazyLoadQueue'; console.log('appImManager included!'); +appSidebarLeft; // just to include + let testScroll = false; export class AppImManager { @@ -343,7 +345,7 @@ export class AppImManager { } let ids = Object.keys(this.bubbles).map(k => +k).filter(id => { - if(!this.scrollable.visibleElements.find(e => e.element == this.bubbles[id])) return false; + //if(!this.scrollable.visibleElements.find(e => e.element == this.bubbles[id])) return false; let message = appMessagesManager.getMessage(id); @@ -370,7 +372,9 @@ export class AppImManager { this.searchBtn.addEventListener('click', (e) => { if(this.peerID) { - appSidebarLeft.beginSearch(this.peerID); + appSidebarRight.beginSearch(); + //appSidebarLeft.archivedCount; + //appSidebarLeft.beginSearch(this.peerID); } }); @@ -912,6 +916,8 @@ export class AppImManager { return true; } + } else { + appSidebarRight.searchCloseBtn.click(); } // clear diff --git a/src/lib/appManagers/appSidebarLeft.ts b/src/lib/appManagers/appSidebarLeft.ts index 85987da9..c91cffce 100644 --- a/src/lib/appManagers/appSidebarLeft.ts +++ b/src/lib/appManagers/appSidebarLeft.ts @@ -1,87 +1,32 @@ -import { logger } from "../polyfill"; -import { formatPhoneNumber } from "../../components/misc"; -import Scrollable from '../../components/scrollable'; -import appMessagesManager from "./appMessagesManager"; +//import { logger } from "../polyfill"; import appDialogsManager from "./appDialogsManager"; -import { isElementInViewport, numberWithCommas, $rootScope } from "../utils"; -import appMessagesIDsManager from "./appMessagesIDsManager"; +import { $rootScope } from "../utils"; import appImManager from "./appImManager"; -import appUsersManager from "./appUsersManager"; import apiManager from "../mtproto/apiManager"; -import appPeersManager from './appPeersManager'; - -class SearchGroup { - container: HTMLDivElement; - nameEl: HTMLDivElement; - list: HTMLUListElement; - - constructor(public name: string, public type: string) { - this.list = document.createElement('ul'); - this.container = document.createElement('div'); - this.nameEl = document.createElement('div'); - this.nameEl.classList.add('search-group__name'); - this.nameEl.innerText = name; - - this.container.classList.add('search-group'); - this.container.append(this.nameEl, this.list); - this.container.style.display = 'none'; - - appDialogsManager.setListClickListener(this.list); - } - - clear() { - this.container.style.display = 'none'; - this.list.innerHTML = ''; - } - - setActive() { - this.container.style.display = ''; - } -} +import AppSearch, { SearchGroup } from "../../components/appSearch"; class AppSidebarLeft { - private sidebarEl = document.querySelector('.page-chats .chats-container') as HTMLDivElement; - private searchInput = document.getElementById('global-search') as HTMLInputElement; + private sidebarEl = document.getElementById('column-left') as HTMLDivElement; private toolsBtn = this.sidebarEl.querySelector('.sidebar-tools-button') as HTMLButtonElement; private backBtn = this.sidebarEl.querySelector('.sidebar-back-button') as HTMLButtonElement; private searchContainer = this.sidebarEl.querySelector('#search-container') as HTMLDivElement; + private searchInput = document.getElementById('global-search') as HTMLInputElement; private menuEl = this.toolsBtn.querySelector('.btn-menu'); private savedBtn = this.menuEl.querySelector('.menu-saved'); private archivedBtn = this.menuEl.querySelector('.menu-archive'); private logOutBtn = this.menuEl.querySelector('.menu-logout'); public archivedCount = this.archivedBtn.querySelector('.archived-count') as HTMLSpanElement; - - private listsContainer: HTMLDivElement = null; - - private log = logger('SL'); - - private peerID = 0; - private minMsgID = 0; - private loadedCount = 0; - private foundCount = 0; - private offsetRate = 0; - - private searchPromise: Promise = null; - private searchTimeout: number = 0; - - private query = ''; - public searchGroups: {[group: string]: SearchGroup} = {}; + //private log = logger('SL'); + + private globalSearch = new AppSearch(this.searchContainer, this.searchInput, { + contacts: new SearchGroup('Contacts and Chats', 'contacts'), + globalContacts: new SearchGroup('Global Search', 'contacts'), + messages: new SearchGroup('Global Search', 'messages') + }); constructor() { - this.searchGroups = { - contacts: new SearchGroup('Contacts and Chats', 'contacts'), - globalContacts: new SearchGroup('Global Search', 'contacts'), - globalMessages: new SearchGroup('Global Search', 'messages'), - privateMessages: new SearchGroup('Private Search', 'messages') - }; - - this.listsContainer = new Scrollable(this.searchContainer).container; - for(let i in this.searchGroups) { - this.listsContainer.append(this.searchGroups[i].container); - } - this.savedBtn.addEventListener('click', (e) => { ///////this.log('savedbtn click'); setTimeout(() => { // menu doesn't close if no timeout (lol) @@ -101,19 +46,17 @@ class AppSidebarLeft { this.logOutBtn.addEventListener('click', (e) => { apiManager.logOut(); }); - - this.listsContainer.addEventListener('scroll', this.onSidebarScroll.bind(this)); this.searchInput.addEventListener('focus', (e) => { this.toolsBtn.classList.remove('active'); this.backBtn.classList.add('active'); this.searchContainer.classList.add('active'); - if(!this.searchInput.value) { - for(let i in this.searchGroups) { - this.searchGroups[i].clear(); + /* if(!this.globalSearch.searchInput.value) { + for(let i in this.globalSearch.searchGroups) { + this.globalSearch.searchGroups[i].clear(); } - } + } */ this.searchInput.addEventListener('blur', (e) => { if(!this.searchInput.value) { @@ -128,46 +71,13 @@ class AppSidebarLeft { this.minMsgID = 0; */ }, {once: true}); }); - - this.searchInput.addEventListener('input', (e) => { - //console.log('messageInput input', this.innerText, serializeNodes(Array.from(messageInput.childNodes))); - let value = this.searchInput.value; - ////////this.log('input', value); - - if(!value.trim()) { - //this.peerID = 0; - return; - } - - this.query = value; - this.minMsgID = 0; - this.loadedCount = 0; - this.foundCount = 0; - this.offsetRate = 0; - - for(let i in this.searchGroups) { - this.searchGroups[i].clear(); - } - - this.searchPromise = null; - this.searchMore(); - }); this.backBtn.addEventListener('click', (e) => { appDialogsManager.chatsArchivedContainer.classList.remove('active'); this.toolsBtn.classList.add('active'); this.backBtn.classList.remove('active'); - this.searchInput.value = ''; this.searchContainer.classList.remove('active'); - this.peerID = 0; - }); - - window.addEventListener('resize', () => { - //this.chatsLoadCount = Math.round(document.body.scrollHeight / 70 * 1.5); - - setTimeout(() => { - this.onSidebarScroll(); - }, 0); + this.globalSearch.reset(); }); $rootScope.$on('dialogs_archived_unread', (e: CustomEvent) => { @@ -178,154 +88,6 @@ class AppSidebarLeft { this.log('got top categories:', categories); }); */ } - - public onSidebarScroll() { - if(!this.query.trim()) return; - - let elements = Array.from(this.searchGroups[this.peerID ? 'privateMessages' : 'globalMessages'].list.childNodes).slice(-5); - for(let li of elements) { - if(isElementInViewport(li)) { - this.log('Will load more search'); - - if(!this.searchTimeout) { - this.searchTimeout = setTimeout(() => { - this.searchMore(); - this.searchTimeout = 0; - }, 0); - } - - break; - } - } - } - - public beginSearch(peerID?: number) { - if(peerID) { - this.peerID = peerID; - } - - this.searchInput.focus(); - } - - private searchMore() { - if(this.searchPromise) return this.searchPromise; - - let query = this.query; - - if(!query.trim()) return; - - if(this.loadedCount != 0 && this.loadedCount >= this.foundCount) { - return Promise.resolve(); - } - - let maxID = appMessagesIDsManager.getMessageIDInfo(this.minMsgID)[0]; - - if(!this.peerID && !maxID) { - appUsersManager.searchContacts(query, 20).then((contacts: any) => { - if(this.searchInput.value != query) { - return; - } - - ///////this.log('input search contacts result:', contacts); - - let setResults = (results: any, group: SearchGroup, showMembersCount = false) => { - results.forEach((inputPeer: any) => { - let peerID = appPeersManager.getPeerID(inputPeer); - let peer = appPeersManager.getPeer(peerID); - let originalDialog = appMessagesManager.getDialogByPeerID(peerID)[0]; - - //////////this.log('contacts peer', peer); - - if(!originalDialog) { - /////////this.log('no original dialog by peerID:', peerID); - - originalDialog = { - peerID: peerID, - pFlags: {}, - peer: peer - }; - } - - let {dialog, dom} = appDialogsManager.addDialog(originalDialog, group.list, false); - - if(showMembersCount && (peer.participants_count || peer.participants)) { - let isChannel = appPeersManager.isChannel(peerID) && !appPeersManager.isMegagroup(peerID); - let participants_count = peer.participants_count || peer.participants.participants.length; - let subtitle = numberWithCommas(participants_count) + ' ' + (isChannel ? 'subscribers' : 'members'); - dom.lastMessageSpan.innerText = subtitle; - } else { - let username = appPeersManager.getPeerUsername(peerID); - if(!username) { - let user = appUsersManager.getUser(peerID); - if(user && user.phone) { - username = '+' + formatPhoneNumber(user.phone).formatted; - } - } else { - username = '@' + username; - } - - dom.lastMessageSpan.innerText = username; - } - }); - - if(results.length) { - group.setActive(); - } - }; - - setResults(contacts.my_results, this.searchGroups.contacts, true); - setResults(contacts.results, this.searchGroups.globalContacts); - }); - } - - return this.searchPromise = appMessagesManager.getSearch(this.peerID, query, null, maxID, 20, this.offsetRate).then(res => { - this.searchPromise = null; - - if(this.searchInput.value != query) { - return; - } - - /////////this.log('input search result:', this.peerID, query, null, maxID, 20, res); - - let {count, history, next_rate} = res; - - if(history[0] == this.minMsgID) { - history.shift(); - } - - let searchGroup = this.searchGroups[this.peerID ? 'privateMessages' : 'globalMessages']; - searchGroup.setActive(); - - history.forEach((msgID: number) => { - let message = appMessagesManager.getMessage(msgID); - let originalDialog = appMessagesManager.getDialogByPeerID(message.peerID)[0]; - - if(!originalDialog) { - ////////this.log('no original dialog by message:', message); - - originalDialog = { - peerID: message.peerID, - pFlags: {}, - peer: message.to_id - }; - } - - let {dialog, dom} = appDialogsManager.addDialog(originalDialog, searchGroup.list, false); - appDialogsManager.setLastMessage(dialog, message, dom); - }); - - this.minMsgID = history[history.length - 1]; - this.offsetRate = next_rate; - this.loadedCount += history.length; - - if(!this.foundCount) { - this.foundCount = count; - } - }).catch(err => { - this.log.error('search error', err); - this.searchPromise = null; - }); - } } const appSidebarLeft = new AppSidebarLeft(); diff --git a/src/lib/appManagers/appSidebarRight.ts b/src/lib/appManagers/appSidebarRight.ts index c2d015d8..e644efee 100644 --- a/src/lib/appManagers/appSidebarRight.ts +++ b/src/lib/appManagers/appSidebarRight.ts @@ -12,12 +12,14 @@ import appImManager from "./appImManager"; import appMediaViewer from "./appMediaViewer"; import LazyLoadQueue from "../../components/lazyLoadQueue"; import { wrapDocument, wrapAudio } from "../../components/wrappers"; +import AppSearch, { SearchGroup } from "../../components/appSearch"; const testScroll = false; class AppSidebarRight { - public sidebarEl = document.querySelector('.profile-container') as HTMLDivElement; - public profileContentEl = document.querySelector('.profile-content') as HTMLDivElement; + public sidebarEl = document.getElementById('column-right') as HTMLDivElement; + public profileContainer = this.sidebarEl.querySelector('.profile-container') as HTMLDivElement; + public profileContentEl = this.sidebarEl.querySelector('.profile-content') as HTMLDivElement; public profileElements = { avatar: this.profileContentEl.querySelector('.profile-avatar') as HTMLDivElement, name: this.profileContentEl.querySelector('.profile-name') as HTMLDivElement, @@ -88,12 +90,19 @@ class AppSidebarRight { } = {}; public urlsToRevoke: string[] = []; + + private searchContainer = this.sidebarEl.querySelector('#search-private-container') as HTMLDivElement; + public searchCloseBtn = this.searchContainer.querySelector('.sidebar-close-button') as HTMLButtonElement; + private searchInput = document.getElementById('private-search') as HTMLInputElement; + public privateSearch = new AppSearch(this.searchContainer.querySelector('.chats-container') as HTMLDivElement, this.searchInput, { + messages: new SearchGroup('Private Search', 'messages') + }); constructor() { let container = this.profileContentEl.querySelector('.profile-tabs-content') as HTMLDivElement; this.profileTabs = this.profileContentEl.querySelector('.profile-tabs') as HTMLUListElement; - this.scroll = new Scrollable(this.sidebarEl, 'y', 1200, 'SR'); + this.scroll = new Scrollable(this.profileContainer, 'y', 1200, 'SR'); this.scroll.container.addEventListener('scroll', this.onSidebarScroll.bind(this)); this.scroll.onScrolledBottom = () => { if(this.sharedMediaSelected && !this.scroll.hiddenElements.down.length && this.sharedMediaSelected.childElementCount/* && false */) { @@ -131,6 +140,11 @@ class AppSidebarRight { sidebarCloseBtn.addEventListener('click', () => { this.toggleSidebar(false); }); + + this.searchCloseBtn.addEventListener('click', () => { + this.searchContainer.classList.remove('active'); + this.privateSearch.reset(); + }); this.sharedMedia.contentMedia.addEventListener('click', (e) => { let target = e.target as HTMLDivElement; @@ -175,6 +189,12 @@ class AppSidebarRight { (this.profileTabs.children[1] as HTMLLIElement).click(); // set media } } + + public beginSearch() { + this.toggleSidebar(true); + this.searchContainer.classList.add('active'); + this.privateSearch.beginSearch(this.peerID); + } public onSidebarScroll() { this.lazyLoadQueueSidebar.check(); @@ -191,12 +211,8 @@ class AppSidebarRight { return; } - - if(this.sidebarEl.classList.contains('active')) { - this.sidebarEl.classList.remove('active'); - } else { - this.sidebarEl.classList.add('active'); - } + + this.sidebarEl.classList.toggle('active'); } public performSearchResult(ids: number[], type: string) { @@ -533,7 +549,7 @@ class AppSidebarRight { this.savedVirtualStates = {}; this.prevTabID = -1; this.scroll.setVirtualContainer(null); - (this.profileTabs.children[1] as HTMLLIElement).click(); // set media + (this.profileTabs.firstElementChild.children[1] as HTMLLIElement).click(); // set media //this.scroll.getScrollTopOffset(); diff --git a/src/lib/richtextprocessor.js b/src/lib/richtextprocessor.js index c9635ea6..bb117692 100644 --- a/src/lib/richtextprocessor.js +++ b/src/lib/richtextprocessor.js @@ -547,6 +547,11 @@ function wrapRichText (text, options = {}) { ) break case 'messageEntityBold': + if(options.noTextFormat) { + html.push(wrapRichNestedText(entityText, entity.nested, options)); + break; + } + html.push( '', wrapRichNestedText(entityText, entity.nested, options), @@ -554,13 +559,30 @@ function wrapRichText (text, options = {}) { ) break case 'messageEntityItalic': + if(options.noTextFormat) { + html.push(wrapRichNestedText(entityText, entity.nested, options)); + break; + } + html.push( '', wrapRichNestedText(entityText, entity.nested, options), '' ) break + case 'messageEntityHighlight': + html.push( + '', + wrapRichNestedText(entityText, entity.nested, options), + '' + ) + break; case 'messageEntityCode': + if(options.noTextFormat) { + html.push(encodeEntities(entityText)); + break; + } + html.push( '', encodeEntities(entityText), @@ -568,6 +590,11 @@ function wrapRichText (text, options = {}) { ) break case 'messageEntityPre': + if(options.noTextFormat) { + html.push(encodeEntities(entityText)); + break; + } + html.push( '
',
           encodeEntities(entityText),
diff --git a/src/lib/utils.js b/src/lib/utils.js
index 56643ef3..089b659c 100644
--- a/src/lib/utils.js
+++ b/src/lib/utils.js
@@ -615,6 +615,13 @@ export function listUniqSorted (list) {
   return resultList
 }
 
+// credits to https://github.com/sindresorhus/escape-string-regexp/blob/master/index.js
+export function escapeRegExp(str) {
+  return str
+    .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&')
+    .replace(/-/g, '\\x2d');
+}
+
 export function encodeEntities (value) {
   return value.replace(/&/g, '&').replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, function (value) {
     var hi = value.charCodeAt(0)
diff --git a/src/scss/partials/_chatlist.scss b/src/scss/partials/_chatlist.scss
index 67e9b400..1d4251e6 100644
--- a/src/scss/partials/_chatlist.scss
+++ b/src/scss/partials/_chatlist.scss
@@ -1,4 +1,6 @@
 .chats-container {
+  position: relative;
+
   .input-search {
     position: relative;
     width: 100%;
@@ -155,8 +157,6 @@
   }
 
   .user-title {
-    max-width: 80%;
-
     img.emoji {
       vertical-align: top;
       width: 18px;
@@ -174,13 +174,6 @@
   }
 
   .user-last-message {
-    max-width: 80%;
-
-    i {
-      font-style: normal;
-      color: $darkblue;
-    }
-
     img.emoji {
       width: 20px;
       height: 20px;
@@ -194,6 +187,15 @@
     }
   }
 
+  .user-title, .user-last-message {
+    max-width: 80%;
+
+    i {
+      font-style: normal;
+      color: $darkblue;
+    }
+  }
+
   .message-status {
     margin-right: .1rem;
     //margin-top: .3rem;
@@ -241,4 +243,17 @@
   .unread-muted, .tgico-pinnedchat {
     background: #c5c9cc;
   }
+
+  .search-group {
+    width: 100%;
+    border-bottom: 1px solid #DADCE0;
+    padding: 1rem 0 .5rem;
+    margin-bottom: .5rem;
+
+    &__name {
+      color: $color-gray;
+      padding: 0 1.85rem;
+      padding-bottom: 1rem;
+    }
+  }
 }
diff --git a/src/scss/partials/_emojiDropdown.scss b/src/scss/partials/_emojiDropdown.scss
index 0bc430a2..56118da4 100644
--- a/src/scss/partials/_emojiDropdown.scss
+++ b/src/scss/partials/_emojiDropdown.scss
@@ -195,7 +195,7 @@
 	}
 
 	.emoji-padding, .stickers-padding {
-		.menu-horizontal > li {
+		.menu-horizontal li {
 			font-size: 1.65rem;
 		}
 	}
diff --git a/src/scss/partials/_leftSidebar.scss b/src/scss/partials/_leftSidebar.scss
index 34eec602..50da26d0 100644
--- a/src/scss/partials/_leftSidebar.scss
+++ b/src/scss/partials/_leftSidebar.scss
@@ -1,43 +1,13 @@
-.chats-container {
+#column-left {
   display: flex;
   flex-direction: column;
 
-  .sidebar-content {
-    width: 100%;
-    max-height: 100%;
-    height: 100%;
-    overflow: hidden;
-    display: flex; /* idk why but need */
-    position: relative;
-
-    > div {
-      width: 100%;
-    }
-  }
-
   #chats-container {
     max-height: 100%;
     overflow: hidden;
     position: relative;
   }
 
-  #search-container, #chats-archived-container {
-    display: none;
-    width: 100%;
-    max-height: 100%;
-    height: 100%;
-    overflow: hidden;
-    position: absolute;
-    left: 0;
-    top: 0;
-    z-index: 3;
-    background: #fff;
-
-    &.active {
-      display: flex;
-    }
-  }
-
   .sidebar-header__btn-container {
     position: relative;
     width: 39.75px;
@@ -97,16 +67,5 @@
     }
   }
 
-  .search-group {
-    width: 100%;
-    border-bottom: 1px solid #DADCE0;
-    padding: 1rem 0 .5rem;
-    margin-bottom: .5rem;
-
-    &__name {
-      color: $color-gray;
-      padding: 0 1.85rem;
-      padding-bottom: 1rem;
-    }
-  }
+  
 }
diff --git a/src/scss/partials/_rightSIdebar.scss b/src/scss/partials/_rightSIdebar.scss
index 23106583..be47f546 100644
--- a/src/scss/partials/_rightSIdebar.scss
+++ b/src/scss/partials/_rightSIdebar.scss
@@ -1,18 +1,20 @@
-.profile-container {
+#column-right {
   width: 0%;
   /* grid-column: 3; */
   position: relative;
   transition: .2s ease-in-out;
 
-  > .scrollable {
-    min-width: 25vw;
-    display: flex;
-    flex-direction: column;
-  }
-
-  @media (min-width: $large-screen) {
+  .profile-container {
     > .scrollable {
-      min-width: calc(#{$large-screen} / 4 - 1px);
+      min-width: 25vw;
+      display: flex;
+      flex-direction: column;
+    }
+  
+    @media (min-width: $large-screen) {
+      > .scrollable {
+        min-width: calc(#{$large-screen} / 4 - 1px);
+      }
     }
   }
 
@@ -27,6 +29,13 @@
   .sidebar-header {
     flex: 0 0 auto;
   }
+
+  #search-private-container {
+    .chats-container {
+      position: relative;
+      flex: 1 1 auto;
+    }
+  }
 }
 
 .profile-content {
diff --git a/src/scss/partials/_sidebar.scss b/src/scss/partials/_sidebar.scss
index 4e9431da..9e025364 100644
--- a/src/scss/partials/_sidebar.scss
+++ b/src/scss/partials/_sidebar.scss
@@ -1,31 +1,44 @@
 .sidebar {
   background-color: #fff;
   overflow: hidden;
-}
 
-.sidebar-left {
-  border-right: 1px solid #DADCE0;
-}
+  &-left {
+    border-right: 1px solid #DADCE0;
+  }
 
-.sidebar-right {
-  border-left: 1px solid #DADCE0;
-}
+  &-right {
+    border-left: 1px solid #DADCE0;
+  }
+
+  &-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 7.5px 20px 7.5px 15px;
+    min-height: 60px;
 
-.sidebar-header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 7.5px 20px 7.5px 15px;
-  min-height: 60px;
+    &__title {
+      flex: 1;
+      padding-left: 2rem;
+      font-weight: 500;
+      font-size: 1.4rem;
+    }
 
-  .sidebar-title {
-    flex: 1;
-    padding-left: 2rem;
-    font-weight: 500;
-    font-size: 1.4rem;
+    .btn-icon + .btn-icon {
+      margin-left: .5rem;
+    }
   }
 
-  .btn-icon + .btn-icon {
-    margin-left: .5rem;
+  &-content {
+    width: 100%;
+    max-height: 100%;
+    height: 100%;
+    overflow: hidden;
+    display: flex; /* idk why but need */
+    position: relative;
+
+    > div {
+      width: 100%;
+    }
   }
 }
diff --git a/src/scss/style.scss b/src/scss/style.scss
index 4f34aff5..2d7e52a2 100644
--- a/src/scss/style.scss
+++ b/src/scss/style.scss
@@ -1422,16 +1422,22 @@ div.scrollable::-webkit-scrollbar-thumb {
 }
 
 .menu-horizontal {
-  width: 100%;
   color: $darkgrey;
-  margin-top: 0px;
-  margin-bottom: 0;
   border-bottom: 1px solid $lightgrey;
-  display: flex;
-  justify-content: space-around;
-  align-items: center;
+  position: relative;
+
+  ul {
+    width: 100%;
+    height: 100%;
+    margin: 0;
+    display: flex;
+    justify-content: space-around;
+    align-items: center;
+    position: relative;
+    z-index: 2;
+  }
   
-  > li {
+  li {
     display: inline-block;
     padding: .75rem 1rem;
     cursor: pointer;
@@ -1439,24 +1445,27 @@ div.scrollable::-webkit-scrollbar-thumb {
     flex: 1;
     user-select: none;
     font-size: 1rem;
-    
+
     &.active {
-      position: relative;
       color: $blue;
-      
-      &:after {
-        content: '';
-        position: absolute;
-        background: $blue;
-        left: 24px;
-        right: 24px;
-        bottom: -1px;
-        height: 4px;
-        border-top-left-radius: 2px;
-        border-top-right-radius: 2px;
-      }
     }
   }
+
+  &__stripe {
+    position: absolute;
+    background: $blue;
+    //left: 0;
+    left: -2px;
+    transition: .3s transform, .3s width;
+    //transition: .3s transform;
+    bottom: -1px;
+    height: 4px;
+    width: 1px; // need if using transform
+    transform: scaleX(1) translateX(0px);
+    border-top-left-radius: 2px;
+    border-top-right-radius: 2px;
+    z-index: 1;
+  }
 }
 
 .tabs-container {
@@ -1493,7 +1502,7 @@ div.scrollable::-webkit-scrollbar-thumb {
 }
 
 .justify-start {
-  justify-content: flex-start;
+  justify-content: flex-start!important;
 }
 
 .popup-send-photo {
@@ -1609,7 +1618,7 @@ div.scrollable::-webkit-scrollbar-thumb {
     max-height: 100%;
   }
   
-  .chats-container {
+  #column-left {
     width: 25%;
     /* grid-column: 1; */
   }
@@ -1635,6 +1644,24 @@ div.scrollable::-webkit-scrollbar-thumb {
     }
   }
 
+  #search-container, #chats-archived-container, .sidebar-search {
+    display: none;
+    flex-direction: column;
+    width: 100%;
+    max-height: 100%;
+    height: 100%;
+    overflow: hidden;
+    position: absolute;
+    left: 0;
+    top: 0;
+    z-index: 3;
+    background: #fff;
+
+    &.active {
+      display: flex;
+    }
+  }
+
   @media (min-width: $large-screen) {
     border-top-width: 0;
     border-bottom-width: 0;