Browse Source

Private search in RS & search highlight query & sidebar stripe animation

master
morethanwords 5 years ago
parent
commit
dd877bee83
  1. 236
      src/components/appSearch.ts
  2. 8
      src/components/emoticonsDropdown.ts
  3. 18
      src/components/misc.ts
  4. 56
      src/lib/appManagers/appDialogsManager.ts
  5. 10
      src/lib/appManagers/appImManager.ts
  6. 270
      src/lib/appManagers/appSidebarLeft.ts
  7. 34
      src/lib/appManagers/appSidebarRight.ts
  8. 27
      src/lib/richtextprocessor.js
  9. 7
      src/lib/utils.js
  10. 33
      src/scss/partials/_chatlist.scss
  11. 2
      src/scss/partials/_emojiDropdown.scss
  12. 43
      src/scss/partials/_leftSidebar.scss
  13. 27
      src/scss/partials/_rightSIdebar.scss
  14. 53
      src/scss/partials/_sidebar.scss
  15. 71
      src/scss/style.scss

236
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<void> = 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, '<i>$1</i>');
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 = '<i>' + username + '</i>';
}
});
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;
});
}
}

8
src/components/emoticonsDropdown.ts

@ -34,7 +34,9 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
lottieLoader.checkAnimations(false, EMOTICONSSTICKERGROUP); lottieLoader.checkAnimations(false, EMOTICONSSTICKERGROUP);
lazyLoadQueue.check(); // for stickers 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) => { let emoticonsMenuOnClick = (menu: HTMLUListElement, heights: number[], scroll: Scrollable, menuScroll?: Scrollable) => {
menu.addEventListener('click', function(e) { menu.addEventListener('click', function(e) {
@ -151,7 +153,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
let heights: number[] = [0]; let heights: number[] = [0];
let prevCategoryIndex = 1; 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); let emojiScroll = new Scrollable(contentEmojiDiv, 'y', 500, 'EMOJI', null);
emojiScroll.container.addEventListener('scroll', (e) => { emojiScroll.container.addEventListener('scroll', (e) => {
prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, emojiScroll.container); prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, emojiScroll.container);
@ -205,7 +207,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
//let stickersDiv = contentStickersDiv.querySelector('.os-content') as HTMLDivElement; //let stickersDiv = contentStickersDiv.querySelector('.os-content') as HTMLDivElement;
let menuWrapper = contentStickersDiv.nextElementSibling 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'); let menuScroll = new Scrollable(menuWrapper, 'x');

18
src/components/misc.ts

@ -1,4 +1,4 @@
import { whichChild, findUpTag, cancelEvent } from "../lib/utils"; import { whichChild, findUpTag } from "../lib/utils";
let rippleClickID = 0; let rippleClickID = 0;
export function ripple(elem: HTMLElement, callback: (id: number) => Promise<boolean | void> = () => Promise.resolve(), onEnd: (id: number) => void = null) { export function ripple(elem: HTMLElement, callback: (id: number) => Promise<boolean | void> = () => Promise.resolve(), onEnd: (id: number) => void = null) {
@ -141,13 +141,13 @@ export function putPreloader(elem: Element, returnDiv = false) {
elem.innerHTML += html; 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 hideTimeout: number = 0;
let prevTabContent: HTMLDivElement = null; let prevTabContent: HTMLDivElement = null;
let prevId = -1; let prevId = -1;
let children = Array.from(content.children); let children = Array.from(content.children);
let tabsChildren = tabs ? Array.from(tabs.children) : []; let tabsChildren = tabs ? Array.from(tabs.firstElementChild.children) : [];
let activeInSlide: Set<Element> = new Set(); let activeInSlide: Set<Element> = new Set();
let selectTab = (id: number) => { let selectTab = (id: number) => {
@ -227,6 +227,11 @@ export function horizontalMenu(tabs: HTMLUListElement, content: HTMLDivElement,
}; };
if(tabs) { if(tabs) {
let activeStripe = document.createElement('span');
activeStripe.classList.add('menu-horizontal__stripe');
tabs.append(activeStripe);
tabs.addEventListener('click', function(e) { tabs.addEventListener('click', function(e) {
let target = e.target as HTMLLIElement; let target = e.target as HTMLLIElement;
@ -242,7 +247,6 @@ export function horizontalMenu(tabs: HTMLUListElement, content: HTMLDivElement,
let tabContent = content.children[id] as HTMLDivElement; let tabContent = content.children[id] as HTMLDivElement;
if(activeInSlide.size >= 2 && !activeInSlide.has(tabContent)) { if(activeInSlide.size >= 2 && !activeInSlide.has(tabContent)) {
cancelEvent(e);
return false; return false;
} }
@ -254,6 +258,12 @@ export function horizontalMenu(tabs: HTMLUListElement, content: HTMLDivElement,
let prev = tabs.querySelector('li.active') as HTMLLIElement; let prev = tabs.querySelector('li.active') as HTMLLIElement;
prev && prev.classList.remove('active'); 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'); target.classList.add('active');
selectTab(id); selectTab(id);
}); });

56
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 appImManager, { AppImManager } from "./appImManager";
import appPeersManager from './appPeersManager'; import appPeersManager from './appPeersManager';
import appMessagesManager, { AppMessagesManager } from "./appMessagesManager"; import appMessagesManager, { AppMessagesManager } from "./appMessagesManager";
@ -420,7 +420,7 @@ export class AppDialogsManager {
chatsHidden.down = inBottom; chatsHidden.down = inBottom;
} }
public setLastMessage(dialog: any, lastMessage?: any, dom?: DialogDom) { public setLastMessage(dialog: any, lastMessage?: any, dom?: DialogDom, highlightWord?: string) {
if(!lastMessage) { if(!lastMessage) {
lastMessage = appMessagesManager.getMessage(dialog.top_message); lastMessage = appMessagesManager.getMessage(dialog.top_message);
} }
@ -497,12 +497,58 @@ export class AppDialogsManager {
} }
if(lastMessage.action) { 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 // @ts-ignore
lastMessageText = langPack[lastMessage.action._]; lastMessageText = '<i>' + langPack[_] + suffix + '</i>';
}
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 + dom.lastMessageSpan.innerHTML = lastMessageText + messageWrapped;
(lastMessage.message ? RichTextProcessor.wrapRichText(lastMessage.message.replace(/\n/g, ' '), {noLinebreakers: true}) : '');
/* if(lastMessage.from_id == auth.id) { // You: */ /* if(lastMessage.from_id == auth.id) { // You: */
if(peer._ != 'peerUser' && peerID != -lastMessage.from_id) { if(peer._ != 'peerUser' && peerID != -lastMessage.from_id) {

10
src/lib/appManagers/appImManager.ts

@ -26,6 +26,8 @@ import LazyLoadQueue from '../../components/lazyLoadQueue';
console.log('appImManager included!'); console.log('appImManager included!');
appSidebarLeft; // just to include
let testScroll = false; let testScroll = false;
export class AppImManager { export class AppImManager {
@ -343,7 +345,7 @@ export class AppImManager {
} }
let ids = Object.keys(this.bubbles).map(k => +k).filter(id => { 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); let message = appMessagesManager.getMessage(id);
@ -370,7 +372,9 @@ export class AppImManager {
this.searchBtn.addEventListener('click', (e) => { this.searchBtn.addEventListener('click', (e) => {
if(this.peerID) { if(this.peerID) {
appSidebarLeft.beginSearch(this.peerID); appSidebarRight.beginSearch();
//appSidebarLeft.archivedCount;
//appSidebarLeft.beginSearch(this.peerID);
} }
}); });
@ -912,6 +916,8 @@ export class AppImManager {
return true; return true;
} }
} else {
appSidebarRight.searchCloseBtn.click();
} }
// clear // clear

270
src/lib/appManagers/appSidebarLeft.ts

@ -1,50 +1,16 @@
import { logger } from "../polyfill"; //import { logger } from "../polyfill";
import { formatPhoneNumber } from "../../components/misc";
import Scrollable from '../../components/scrollable';
import appMessagesManager from "./appMessagesManager";
import appDialogsManager from "./appDialogsManager"; import appDialogsManager from "./appDialogsManager";
import { isElementInViewport, numberWithCommas, $rootScope } from "../utils"; import { $rootScope } from "../utils";
import appMessagesIDsManager from "./appMessagesIDsManager";
import appImManager from "./appImManager"; import appImManager from "./appImManager";
import appUsersManager from "./appUsersManager";
import apiManager from "../mtproto/apiManager"; import apiManager from "../mtproto/apiManager";
import appPeersManager from './appPeersManager'; import AppSearch, { SearchGroup } from "../../components/appSearch";
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 = '';
}
}
class AppSidebarLeft { class AppSidebarLeft {
private sidebarEl = document.querySelector('.page-chats .chats-container') as HTMLDivElement; private sidebarEl = document.getElementById('column-left') as HTMLDivElement;
private searchInput = document.getElementById('global-search') as HTMLInputElement;
private toolsBtn = this.sidebarEl.querySelector('.sidebar-tools-button') as HTMLButtonElement; private toolsBtn = this.sidebarEl.querySelector('.sidebar-tools-button') as HTMLButtonElement;
private backBtn = this.sidebarEl.querySelector('.sidebar-back-button') as HTMLButtonElement; private backBtn = this.sidebarEl.querySelector('.sidebar-back-button') as HTMLButtonElement;
private searchContainer = this.sidebarEl.querySelector('#search-container') as HTMLDivElement; 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 menuEl = this.toolsBtn.querySelector('.btn-menu');
private savedBtn = this.menuEl.querySelector('.menu-saved'); private savedBtn = this.menuEl.querySelector('.menu-saved');
@ -52,36 +18,15 @@ class AppSidebarLeft {
private logOutBtn = this.menuEl.querySelector('.menu-logout'); private logOutBtn = this.menuEl.querySelector('.menu-logout');
public archivedCount = this.archivedBtn.querySelector('.archived-count') as HTMLSpanElement; public archivedCount = this.archivedBtn.querySelector('.archived-count') as HTMLSpanElement;
private listsContainer: HTMLDivElement = null; //private log = logger('SL');
private log = logger('SL');
private peerID = 0;
private minMsgID = 0;
private loadedCount = 0;
private foundCount = 0;
private offsetRate = 0;
private searchPromise: Promise<void> = null; private globalSearch = new AppSearch(this.searchContainer, this.searchInput, {
private searchTimeout: number = 0; contacts: new SearchGroup('Contacts and Chats', 'contacts'),
globalContacts: new SearchGroup('Global Search', 'contacts'),
private query = ''; messages: new SearchGroup('Global Search', 'messages')
});
public searchGroups: {[group: string]: SearchGroup} = {};
constructor() { 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.savedBtn.addEventListener('click', (e) => {
///////this.log('savedbtn click'); ///////this.log('savedbtn click');
setTimeout(() => { // menu doesn't close if no timeout (lol) setTimeout(() => { // menu doesn't close if no timeout (lol)
@ -102,18 +47,16 @@ class AppSidebarLeft {
apiManager.logOut(); apiManager.logOut();
}); });
this.listsContainer.addEventListener('scroll', this.onSidebarScroll.bind(this));
this.searchInput.addEventListener('focus', (e) => { this.searchInput.addEventListener('focus', (e) => {
this.toolsBtn.classList.remove('active'); this.toolsBtn.classList.remove('active');
this.backBtn.classList.add('active'); this.backBtn.classList.add('active');
this.searchContainer.classList.add('active'); this.searchContainer.classList.add('active');
if(!this.searchInput.value) { /* if(!this.globalSearch.searchInput.value) {
for(let i in this.searchGroups) { for(let i in this.globalSearch.searchGroups) {
this.searchGroups[i].clear(); this.globalSearch.searchGroups[i].clear();
} }
} } */
this.searchInput.addEventListener('blur', (e) => { this.searchInput.addEventListener('blur', (e) => {
if(!this.searchInput.value) { if(!this.searchInput.value) {
@ -129,45 +72,12 @@ class AppSidebarLeft {
}, {once: true}); }, {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) => { this.backBtn.addEventListener('click', (e) => {
appDialogsManager.chatsArchivedContainer.classList.remove('active'); appDialogsManager.chatsArchivedContainer.classList.remove('active');
this.toolsBtn.classList.add('active'); this.toolsBtn.classList.add('active');
this.backBtn.classList.remove('active'); this.backBtn.classList.remove('active');
this.searchInput.value = '';
this.searchContainer.classList.remove('active'); this.searchContainer.classList.remove('active');
this.peerID = 0; this.globalSearch.reset();
});
window.addEventListener('resize', () => {
//this.chatsLoadCount = Math.round(document.body.scrollHeight / 70 * 1.5);
setTimeout(() => {
this.onSidebarScroll();
}, 0);
}); });
$rootScope.$on('dialogs_archived_unread', (e: CustomEvent) => { $rootScope.$on('dialogs_archived_unread', (e: CustomEvent) => {
@ -178,154 +88,6 @@ class AppSidebarLeft {
this.log('got top categories:', categories); 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(); const appSidebarLeft = new AppSidebarLeft();

34
src/lib/appManagers/appSidebarRight.ts

@ -12,12 +12,14 @@ import appImManager from "./appImManager";
import appMediaViewer from "./appMediaViewer"; import appMediaViewer from "./appMediaViewer";
import LazyLoadQueue from "../../components/lazyLoadQueue"; import LazyLoadQueue from "../../components/lazyLoadQueue";
import { wrapDocument, wrapAudio } from "../../components/wrappers"; import { wrapDocument, wrapAudio } from "../../components/wrappers";
import AppSearch, { SearchGroup } from "../../components/appSearch";
const testScroll = false; const testScroll = false;
class AppSidebarRight { class AppSidebarRight {
public sidebarEl = document.querySelector('.profile-container') as HTMLDivElement; public sidebarEl = document.getElementById('column-right') as HTMLDivElement;
public profileContentEl = document.querySelector('.profile-content') as HTMLDivElement; public profileContainer = this.sidebarEl.querySelector('.profile-container') as HTMLDivElement;
public profileContentEl = this.sidebarEl.querySelector('.profile-content') as HTMLDivElement;
public profileElements = { public profileElements = {
avatar: this.profileContentEl.querySelector('.profile-avatar') as HTMLDivElement, avatar: this.profileContentEl.querySelector('.profile-avatar') as HTMLDivElement,
name: this.profileContentEl.querySelector('.profile-name') as HTMLDivElement, name: this.profileContentEl.querySelector('.profile-name') as HTMLDivElement,
@ -89,11 +91,18 @@ class AppSidebarRight {
public urlsToRevoke: string[] = []; 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() { constructor() {
let container = this.profileContentEl.querySelector('.profile-tabs-content') as HTMLDivElement; let container = this.profileContentEl.querySelector('.profile-tabs-content') as HTMLDivElement;
this.profileTabs = this.profileContentEl.querySelector('.profile-tabs') as HTMLUListElement; 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.container.addEventListener('scroll', this.onSidebarScroll.bind(this));
this.scroll.onScrolledBottom = () => { this.scroll.onScrolledBottom = () => {
if(this.sharedMediaSelected && !this.scroll.hiddenElements.down.length && this.sharedMediaSelected.childElementCount/* && false */) { if(this.sharedMediaSelected && !this.scroll.hiddenElements.down.length && this.sharedMediaSelected.childElementCount/* && false */) {
@ -132,6 +141,11 @@ class AppSidebarRight {
this.toggleSidebar(false); this.toggleSidebar(false);
}); });
this.searchCloseBtn.addEventListener('click', () => {
this.searchContainer.classList.remove('active');
this.privateSearch.reset();
});
this.sharedMedia.contentMedia.addEventListener('click', (e) => { this.sharedMedia.contentMedia.addEventListener('click', (e) => {
let target = e.target as HTMLDivElement; let target = e.target as HTMLDivElement;
@ -176,6 +190,12 @@ class AppSidebarRight {
} }
} }
public beginSearch() {
this.toggleSidebar(true);
this.searchContainer.classList.add('active');
this.privateSearch.beginSearch(this.peerID);
}
public onSidebarScroll() { public onSidebarScroll() {
this.lazyLoadQueueSidebar.check(); this.lazyLoadQueueSidebar.check();
} }
@ -192,11 +212,7 @@ class AppSidebarRight {
return; return;
} }
if(this.sidebarEl.classList.contains('active')) { this.sidebarEl.classList.toggle('active');
this.sidebarEl.classList.remove('active');
} else {
this.sidebarEl.classList.add('active');
}
} }
public performSearchResult(ids: number[], type: string) { public performSearchResult(ids: number[], type: string) {
@ -533,7 +549,7 @@ class AppSidebarRight {
this.savedVirtualStates = {}; this.savedVirtualStates = {};
this.prevTabID = -1; this.prevTabID = -1;
this.scroll.setVirtualContainer(null); 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(); //this.scroll.getScrollTopOffset();

27
src/lib/richtextprocessor.js

@ -547,6 +547,11 @@ function wrapRichText (text, options = {}) {
) )
break break
case 'messageEntityBold': case 'messageEntityBold':
if(options.noTextFormat) {
html.push(wrapRichNestedText(entityText, entity.nested, options));
break;
}
html.push( html.push(
'<strong>', '<strong>',
wrapRichNestedText(entityText, entity.nested, options), wrapRichNestedText(entityText, entity.nested, options),
@ -554,13 +559,30 @@ function wrapRichText (text, options = {}) {
) )
break break
case 'messageEntityItalic': case 'messageEntityItalic':
if(options.noTextFormat) {
html.push(wrapRichNestedText(entityText, entity.nested, options));
break;
}
html.push( html.push(
'<em>', '<em>',
wrapRichNestedText(entityText, entity.nested, options), wrapRichNestedText(entityText, entity.nested, options),
'</em>' '</em>'
) )
break break
case 'messageEntityHighlight':
html.push(
'<i>',
wrapRichNestedText(entityText, entity.nested, options),
'</i>'
)
break;
case 'messageEntityCode': case 'messageEntityCode':
if(options.noTextFormat) {
html.push(encodeEntities(entityText));
break;
}
html.push( html.push(
'<code>', '<code>',
encodeEntities(entityText), encodeEntities(entityText),
@ -568,6 +590,11 @@ function wrapRichText (text, options = {}) {
) )
break break
case 'messageEntityPre': case 'messageEntityPre':
if(options.noTextFormat) {
html.push(encodeEntities(entityText));
break;
}
html.push( html.push(
'<pre><code', (entity.language ? ' class="language-' + encodeEntities(entity.language) + '"' : ''), '>', '<pre><code', (entity.language ? ' class="language-' + encodeEntities(entity.language) + '"' : ''), '>',
encodeEntities(entityText), encodeEntities(entityText),

7
src/lib/utils.js

@ -615,6 +615,13 @@ export function listUniqSorted (list) {
return resultList 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) { export function encodeEntities (value) {
return value.replace(/&/g, '&amp;').replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, function (value) { return value.replace(/&/g, '&amp;').replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, function (value) {
var hi = value.charCodeAt(0) var hi = value.charCodeAt(0)

33
src/scss/partials/_chatlist.scss

@ -1,4 +1,6 @@
.chats-container { .chats-container {
position: relative;
.input-search { .input-search {
position: relative; position: relative;
width: 100%; width: 100%;
@ -155,8 +157,6 @@
} }
.user-title { .user-title {
max-width: 80%;
img.emoji { img.emoji {
vertical-align: top; vertical-align: top;
width: 18px; width: 18px;
@ -174,13 +174,6 @@
} }
.user-last-message { .user-last-message {
max-width: 80%;
i {
font-style: normal;
color: $darkblue;
}
img.emoji { img.emoji {
width: 20px; width: 20px;
height: 20px; height: 20px;
@ -194,6 +187,15 @@
} }
} }
.user-title, .user-last-message {
max-width: 80%;
i {
font-style: normal;
color: $darkblue;
}
}
.message-status { .message-status {
margin-right: .1rem; margin-right: .1rem;
//margin-top: .3rem; //margin-top: .3rem;
@ -241,4 +243,17 @@
.unread-muted, .tgico-pinnedchat { .unread-muted, .tgico-pinnedchat {
background: #c5c9cc; 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;
}
}
} }

2
src/scss/partials/_emojiDropdown.scss

@ -195,7 +195,7 @@
} }
.emoji-padding, .stickers-padding { .emoji-padding, .stickers-padding {
.menu-horizontal > li { .menu-horizontal li {
font-size: 1.65rem; font-size: 1.65rem;
} }
} }

43
src/scss/partials/_leftSidebar.scss

@ -1,43 +1,13 @@
.chats-container { #column-left {
display: flex; display: flex;
flex-direction: column; 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 { #chats-container {
max-height: 100%; max-height: 100%;
overflow: hidden; overflow: hidden;
position: relative; 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 { .sidebar-header__btn-container {
position: relative; position: relative;
width: 39.75px; 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;
}
}
} }

27
src/scss/partials/_rightSIdebar.scss

@ -1,18 +1,20 @@
.profile-container { #column-right {
width: 0%; width: 0%;
/* grid-column: 3; */ /* grid-column: 3; */
position: relative; position: relative;
transition: .2s ease-in-out; transition: .2s ease-in-out;
> .scrollable { .profile-container {
min-width: 25vw;
display: flex;
flex-direction: column;
}
@media (min-width: $large-screen) {
> .scrollable { > .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 { .sidebar-header {
flex: 0 0 auto; flex: 0 0 auto;
} }
#search-private-container {
.chats-container {
position: relative;
flex: 1 1 auto;
}
}
} }
.profile-content { .profile-content {

53
src/scss/partials/_sidebar.scss

@ -1,31 +1,44 @@
.sidebar { .sidebar {
background-color: #fff; background-color: #fff;
overflow: hidden; overflow: hidden;
}
.sidebar-left { &-left {
border-right: 1px solid #DADCE0; border-right: 1px solid #DADCE0;
} }
.sidebar-right { &-right {
border-left: 1px solid #DADCE0; 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 { &__title {
display: flex; flex: 1;
align-items: center; padding-left: 2rem;
justify-content: space-between; font-weight: 500;
padding: 7.5px 20px 7.5px 15px; font-size: 1.4rem;
min-height: 60px; }
.sidebar-title { .btn-icon + .btn-icon {
flex: 1; margin-left: .5rem;
padding-left: 2rem; }
font-weight: 500;
font-size: 1.4rem;
} }
.btn-icon + .btn-icon { &-content {
margin-left: .5rem; width: 100%;
max-height: 100%;
height: 100%;
overflow: hidden;
display: flex; /* idk why but need */
position: relative;
> div {
width: 100%;
}
} }
} }

71
src/scss/style.scss

@ -1422,16 +1422,22 @@ div.scrollable::-webkit-scrollbar-thumb {
} }
.menu-horizontal { .menu-horizontal {
width: 100%;
color: $darkgrey; color: $darkgrey;
margin-top: 0px;
margin-bottom: 0;
border-bottom: 1px solid $lightgrey; border-bottom: 1px solid $lightgrey;
display: flex; position: relative;
justify-content: space-around;
align-items: center;
> li { ul {
width: 100%;
height: 100%;
margin: 0;
display: flex;
justify-content: space-around;
align-items: center;
position: relative;
z-index: 2;
}
li {
display: inline-block; display: inline-block;
padding: .75rem 1rem; padding: .75rem 1rem;
cursor: pointer; cursor: pointer;
@ -1441,22 +1447,25 @@ div.scrollable::-webkit-scrollbar-thumb {
font-size: 1rem; font-size: 1rem;
&.active { &.active {
position: relative;
color: $blue; 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 { .tabs-container {
@ -1493,7 +1502,7 @@ div.scrollable::-webkit-scrollbar-thumb {
} }
.justify-start { .justify-start {
justify-content: flex-start; justify-content: flex-start!important;
} }
.popup-send-photo { .popup-send-photo {
@ -1609,7 +1618,7 @@ div.scrollable::-webkit-scrollbar-thumb {
max-height: 100%; max-height: 100%;
} }
.chats-container { #column-left {
width: 25%; width: 25%;
/* grid-column: 1; */ /* 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) { @media (min-width: $large-screen) {
border-top-width: 0; border-top-width: 0;
border-bottom-width: 0; border-bottom-width: 0;

Loading…
Cancel
Save