Browse Source

Handle RTL on search inputs

Debounce pinned message animation
Debounce lazy load queue
master
Eduard Kuzmenko 4 years ago
parent
commit
285e56f233
  1. 4
      src/components/appSearch.ts
  2. 14
      src/components/chat/bubbles.ts
  3. 24
      src/components/chat/pinnedMessage.ts
  4. 17
      src/components/chat/search.ts
  5. 5
      src/components/emoticonsDropdown/index.ts
  6. 48
      src/components/inputField.ts
  7. 26
      src/components/inputSearch.ts
  8. 11
      src/components/lazyLoadQueue.ts
  9. 2
      src/components/scrollable.ts
  10. 18
      src/components/sidebarLeft/index.ts
  11. 10
      src/components/sidebarLeft/tabs/contacts.ts
  12. 9
      src/components/sidebarRight/index.ts
  13. 14
      src/components/sidebarRight/tabs/gifs.ts
  14. 10
      src/components/sidebarRight/tabs/search.ts
  15. 14
      src/components/sidebarRight/tabs/stickers.ts
  16. 4
      src/components/wrappers.ts
  17. 125
      src/helpers/schedulers.ts
  18. 2
      src/index.hbs
  19. 4
      src/lib/mtproto/apiFileManager.ts
  20. 12
      src/lib/mtproto/tl_utils.ts
  21. 3
      src/pages/pageSignQR.ts
  22. 29
      src/scss/partials/_chatPinned.scss
  23. 67
      src/scss/partials/_chatlist.scss
  24. 83
      src/scss/partials/_input.scss
  25. 13
      src/scss/style.scss
  26. 6
      src/types.d.ts

4
src/components/appSearch.ts

@ -6,7 +6,7 @@ import appPeersManager from '../lib/appManagers/appPeersManager'; @@ -6,7 +6,7 @@ import appPeersManager from '../lib/appManagers/appPeersManager';
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import { formatPhoneNumber } from "./misc";
import appChatsManager from "../lib/appManagers/appChatsManager";
import SearchInput from "./searchInput";
import InputSearch from "./inputSearch";
import rootScope from "../lib/rootScope";
import { escapeRegExp } from "../helpers/string";
import searchIndexManager from "../lib/searchIndexManager";
@ -81,7 +81,7 @@ export default class AppSearch { @@ -81,7 +81,7 @@ export default class AppSearch {
private scrollable: Scrollable;
constructor(public container: HTMLElement, public searchInput: SearchInput, public searchGroups: {[group in SearchGroupType]: SearchGroup}, public onSearch?: (count: number) => void) {
constructor(public container: HTMLElement, public searchInput: InputSearch, public searchGroups: {[group in SearchGroupType]: SearchGroup}, public onSearch?: (count: number) => void) {
this.scrollable = new Scrollable(this.container);
this.listsContainer = this.scrollable.container as HTMLDivElement;
for(let i in this.searchGroups) {

14
src/components/chat/bubbles.ts

@ -35,6 +35,7 @@ import LazyLoadQueue from "../lazyLoadQueue"; @@ -35,6 +35,7 @@ import LazyLoadQueue from "../lazyLoadQueue";
import { AppChatsManager } from "../../lib/appManagers/appChatsManager";
import Chat from "./chat";
import ListenerSetter from "../../helpers/listenerSetter";
import { pause } from "../../helpers/schedulers";
const IGNORE_ACTIONS = ['messageActionHistoryClear'];
@ -1123,9 +1124,12 @@ export default class ChatBubbles { @@ -1123,9 +1124,12 @@ export default class ChatBubbles {
bubble = this.findNextMountedBubbleByMsgId(lastMsgId);
}
this.scrollable.scrollIntoView(bubble, samePeer/* , fromUp */);
if(!forwardingUnread) {
this.highlightBubble(bubble);
// ! sometimes there can be no bubble
if(bubble) {
this.scrollable.scrollIntoView(bubble, samePeer/* , fromUp */);
if(!forwardingUnread) {
this.highlightBubble(bubble);
}
}
} else {
this.scrollable.scrollTop = this.scrollable.scrollHeight;
@ -1263,7 +1267,9 @@ export default class ChatBubbles { @@ -1263,7 +1267,9 @@ export default class ChatBubbles {
}
});
resolve();
//setTimeout(() => {
resolve();
//}, 500);
this.messagesQueuePromise = null;
}, reject);
}, 0);

24
src/components/chat/pinnedMessage.ts

@ -11,6 +11,7 @@ import { cancelEvent, findUpClassName, getElementByPoint, handleScrollSideEvent @@ -11,6 +11,7 @@ import { cancelEvent, findUpClassName, getElementByPoint, handleScrollSideEvent
import Chat from "./chat";
import ListenerSetter from "../../helpers/listenerSetter";
import ButtonIcon from "../buttonIcon";
import { debounce } from "../../helpers/schedulers";
class AnimatedSuper {
static DURATION = 200;
@ -94,6 +95,17 @@ class AnimatedSuper { @@ -94,6 +95,17 @@ class AnimatedSuper {
row.element.classList.toggle('is-hiding', false);
previousRow && previousRow.element.classList.toggle('is-hiding', true);
/* const height = row.element.getBoundingClientRect().height;
row.element.style.transform = `translateY(${fromTop ? height * -1 : height}px)`;
if(previousRow) {
previousRow.element.style.transform = `translateY(${fromTop ? height : height * -1}px)`;
} */
/* row.element.style.setProperty('--height', row.element.getBoundingClientRect().height + 'px');
if(previousRow) {
previousRow.element.style.setProperty('--height', previousRow.element.getBoundingClientRect().height + 'px');
} */
this.clearRows(index);
}
}
@ -227,6 +239,8 @@ export default class ChatPinnedMessage { @@ -227,6 +239,8 @@ export default class ChatPinnedMessage {
public getCurrentIndexPromise: Promise<any> = null;
public btnOpen: HTMLButtonElement;
public setPinnedMessage: () => void;
constructor(private topbar: ChatTopbar, private chat: Chat, private appMessagesManager: AppMessagesManager, private appPeersManager: AppPeersManager) {
this.listenerSetter = new ListenerSetter();
@ -290,6 +304,10 @@ export default class ChatPinnedMessage { @@ -290,6 +304,10 @@ export default class ChatPinnedMessage {
this.pinnedMessageContainer.toggle(this.hidden = true);
}
});
// * 200 - no lags
// * 100 - need test
this.setPinnedMessage = debounce(() => this._setPinnedMessage(), 100, true, true);
}
public destroy() {
@ -300,6 +318,8 @@ export default class ChatPinnedMessage { @@ -300,6 +318,8 @@ export default class ChatPinnedMessage {
}
public setCorrectIndex(lastScrollDirection?: number) {
//return;
if(this.locked || this.hidden/* || this.chat.setPeerPromise || this.chat.bubbles.messagesQueuePromise */) {
return;
}
@ -315,6 +335,8 @@ export default class ChatPinnedMessage { @@ -315,6 +335,8 @@ export default class ChatPinnedMessage {
el = findUpClassName(el, 'bubble');
if(!el) return;
//return;
const mid = el.dataset.mid;
if(el && mid !== undefined) {
this.chat.log('[PM]: setCorrectIndex will test mid:', mid);
@ -517,7 +539,7 @@ export default class ChatPinnedMessage { @@ -517,7 +539,7 @@ export default class ChatPinnedMessage {
/* || (!this.chatAudio.divAndCaption.container.classList.contains('hide') && to == ScreenSize.medium) */);
}
public setPinnedMessage() {
public _setPinnedMessage() {
/////this.log('setting pinned message', message);
//return;
/* const promise: Promise<any> = this.chat.setPeerPromise || this.chat.bubbles.messagesQueuePromise || Promise.resolve();

17
src/components/chat/search.ts

@ -1,16 +1,15 @@ @@ -1,16 +1,15 @@
import type ChatTopbar from "./topbar";
import rootScope from "../../lib/rootScope";
import { cancelEvent, whichChild, findUpTag } from "../../helpers/dom";
import AppSearch, { SearchGroup } from "../appSearch";
import PopupDatePicker from "../popupDatepicker";
import { ripple } from "../ripple";
import SearchInput from "../searchInput";
import InputSearch from "../inputSearch";
import type Chat from "./chat";
export default class ChatSearch {
private element: HTMLElement;
private backBtn: HTMLElement;
private searchInput: SearchInput;
private inputSearch: InputSearch;
private results: HTMLElement;
@ -39,7 +38,7 @@ export default class ChatSearch { @@ -39,7 +38,7 @@ export default class ChatSearch {
this.backBtn.addEventListener('click', () => {
this.topbar.container.classList.remove('hide-pinned');
this.element.remove();
this.searchInput.remove();
this.inputSearch.remove();
this.results.remove();
this.footer.remove();
this.footer.removeEventListener('click', this.onFooterClick);
@ -50,7 +49,7 @@ export default class ChatSearch { @@ -50,7 +49,7 @@ export default class ChatSearch {
this.chat.bubbles.bubblesContainer.classList.remove('search-results-active');
}, {once: true});
this.searchInput = new SearchInput('Search');
this.inputSearch = new InputSearch('Search');
// Results
this.results = document.createElement('div');
@ -59,13 +58,13 @@ export default class ChatSearch { @@ -59,13 +58,13 @@ export default class ChatSearch {
this.searchGroup = new SearchGroup('', 'messages', undefined, '', false);
this.searchGroup.list.addEventListener('click', this.onResultsClick);
this.appSearch = new AppSearch(this.results, this.searchInput, {
this.appSearch = new AppSearch(this.results, this.inputSearch, {
messages: this.searchGroup
}, (count) => {
this.foundCount = count;
if(!this.foundCount) {
this.foundCountEl.innerText = this.searchInput.value ? 'No results' : '';
this.foundCountEl.innerText = this.inputSearch.value ? 'No results' : '';
this.results.classList.remove('active');
this.chat.bubbles.bubblesContainer.classList.remove('search-results-active');
this.upBtn.setAttribute('disabled', 'true');
@ -113,12 +112,12 @@ export default class ChatSearch { @@ -113,12 +112,12 @@ export default class ChatSearch {
this.topbar.container.parentElement.insertBefore(this.footer, chat.input.chatInput);
// Append container
this.element.append(this.backBtn, this.searchInput.container);
this.element.append(this.backBtn, this.inputSearch.container);
this.topbar.container.classList.add('hide-pinned');
this.topbar.container.parentElement.append(this.element);
this.searchInput.input.focus();
this.inputSearch.input.focus();
}
onDateClick = (e: MouseEvent) => {

5
src/components/emoticonsDropdown/index.ts

@ -13,6 +13,7 @@ import StickyIntersector from "../stickyIntersector"; @@ -13,6 +13,7 @@ import StickyIntersector from "../stickyIntersector";
import EmojiTab from "./tabs/emoji";
import GifsTab from "./tabs/gifs";
import StickersTab from "./tabs/stickers";
import { pause } from "../../helpers/schedulers";
export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown';
@ -211,9 +212,7 @@ export class EmoticonsDropdown { @@ -211,9 +212,7 @@ export class EmoticonsDropdown {
appImManager.chat.input.saveScroll();
// @ts-ignore
document.activeElement.blur();
await new Promise((resolve) => {
setTimeout(resolve, 100);
});
await pause(100);
}
}

48
src/components/inputField.ts

@ -34,6 +34,22 @@ let init = () => { @@ -34,6 +34,22 @@ let init = () => {
init = null;
};
const checkAndSetRTL = (input: HTMLElement) => {
//const isEmpty = isInputEmpty(input);
//console.log('input', isEmpty);
//const char = [...getRichValue(input)][0];
const char = (input instanceof HTMLInputElement ? input.value : input.innerText)[0];
let direction = 'ltr';
if(char && checkRTL(char)) {
direction = 'rtl';
}
//console.log('RTL', direction, char);
input.style.direction = direction;
};
const InputField = (options: {
placeholder?: string,
label?: string,
@ -51,6 +67,7 @@ const InputField = (options: { @@ -51,6 +67,7 @@ const InputField = (options: {
const {placeholder, label, maxLength, showLengthOn, name, plainText} = options;
let input: HTMLElement;
if(!plainText) {
if(init) {
init();
@ -61,21 +78,9 @@ const InputField = (options: { @@ -61,21 +78,9 @@ const InputField = (options: {
${label ? `<label>${label}</label>` : ''}
`;
const input = div.firstElementChild as HTMLElement;
const observer = new MutationObserver((mutationsList, observer) => {
//const isEmpty = isInputEmpty(input);
//console.log('input', isEmpty);
//const char = [...getRichValue(input)][0];
const char = input.innerText[0];
let direction = 'ltr';
if(char && checkRTL(char)) {
direction = 'rtl';
}
//console.log('RTL', direction, char);
input.style.direction = direction;
input = div.firstElementChild as HTMLElement;
const observer = new MutationObserver(() => {
checkAndSetRTL(input);
if(processInput) {
processInput();
@ -86,21 +91,23 @@ const InputField = (options: { @@ -86,21 +91,23 @@ const InputField = (options: {
observer.observe(input, {characterData: true, childList: true, subtree: true});
} else {
div.innerHTML = `
<input type="text" name="${name}" ${placeholder ? `placeholder="${placeholder}"` : ''} autocomplete="off" required="" class="input-field-input">
<input type="text" ${name ? `name="${name}"` : ''} ${placeholder ? `placeholder="${placeholder}"` : ''} autocomplete="off" ${label ? 'required=""' : ''} class="input-field-input">
${label ? `<label>${label}</label>` : ''}
`;
input = div.firstElementChild as HTMLElement;
input.addEventListener('input', () => checkAndSetRTL(input));
}
let processInput: () => void;
if(maxLength) {
const input = div.firstElementChild as HTMLInputElement;
const labelEl = div.lastElementChild as HTMLLabelElement;
let showingLength = false;
processInput = () => {
const wasError = input.classList.contains('error');
// * https://stackoverflow.com/a/54369605 #2 to count emoji as 1 symbol
const inputLength = plainText ? input.value.length : [...getRichValue(input)].length;
const inputLength = plainText ? (input as HTMLInputElement).value.length : [...getRichValue(input)].length;
const diff = maxLength - inputLength;
const isError = diff < 0;
input.classList.toggle('error', isError);
@ -117,7 +124,10 @@ const InputField = (options: { @@ -117,7 +124,10 @@ const InputField = (options: {
input.addEventListener('input', processInput);
}
return {container: div, input: div.firstElementChild as HTMLInputElement};
return {
container: div,
input: div.firstElementChild as HTMLInputElement
};
};
export default InputField;

26
src/components/searchInput.ts → src/components/inputSearch.ts

@ -1,4 +1,7 @@ @@ -1,4 +1,7 @@
export default class SearchInput {
//import { getRichValue } from "../helpers/dom";
import InputField from "./inputField";
export default class InputSearch {
public container: HTMLElement;
public input: HTMLInputElement;
public clearBtn: HTMLElement;
@ -8,15 +11,19 @@ export default class SearchInput { @@ -8,15 +11,19 @@ export default class SearchInput {
public onChange: (value: string) => void;
constructor(placeholder: string, onChange?: (value: string) => void) {
this.container = document.createElement('div');
const inputField = InputField({
placeholder,
plainText: true
});
this.container = inputField.container;
this.container.classList.remove('input-field');
this.container.classList.add('input-search');
this.onChange = onChange;
this.input = document.createElement('input');
this.input.type = 'text';
this.input.placeholder = placeholder;
this.input.autocomplete = Math.random().toString(36).substring(7);
this.input = inputField.input;
this.input.classList.add('input-search-input');
const searchIcon = document.createElement('span');
searchIcon.classList.add('tgico', 'tgico-search');
@ -33,7 +40,7 @@ export default class SearchInput { @@ -33,7 +40,7 @@ export default class SearchInput {
onInput = () => {
if(!this.onChange) return;
let value = this.input.value;
let value = this.value;
//this.input.classList.toggle('is-empty', !value.trim());
@ -53,12 +60,17 @@ export default class SearchInput { @@ -53,12 +60,17 @@ export default class SearchInput {
get value() {
return this.input.value;
//return getRichValue(this.input);
}
set value(value: string) {
//this.input.innerHTML = value;
this.input.value = value;
this.prevValue = value;
clearTimeout(this.timeout);
const event = new Event('input', {bubbles: true, cancelable: true});
this.input.dispatchEvent(event);
}
public remove() {

11
src/components/lazyLoadQueue.ts

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import { debounce } from "../helpers/schedulers";
import { logger, LogLevels } from "../lib/logger";
import VisibilityIntersector, { OnVisibilityChange } from "./visibilityIntersector";
@ -22,8 +23,10 @@ export class LazyLoadQueueBase { @@ -22,8 +23,10 @@ export class LazyLoadQueueBase {
protected unlockResolve: () => void = null;
protected log = logger('LL', LogLevels.error);
protected processQueue: () => void;
constructor(protected parallelLimit = PARALLEL_LIMIT) {
this.processQueue = debounce(() => this._processQueue(), 20, false, true);
}
public clear() {
@ -58,7 +61,7 @@ export class LazyLoadQueueBase { @@ -58,7 +61,7 @@ export class LazyLoadQueueBase {
this.processQueue();
}
public async processItem(item: LazyLoadElementBase) {
protected async processItem(item: LazyLoadElementBase) {
if(this.lockPromise) {
return;
}
@ -96,7 +99,7 @@ export class LazyLoadQueueBase { @@ -96,7 +99,7 @@ export class LazyLoadQueueBase {
this.processQueue();
}
public async processQueue(item?: LazyLoadElementBase) {
protected _processQueue(item?: LazyLoadElementBase) {
if(!this.queue.length || this.lockPromise || (this.parallelLimit > 0 && this.inProcess.size >= this.parallelLimit)) return;
do {
@ -236,9 +239,9 @@ export default class LazyLoadQueue extends LazyLoadQueueIntersector { @@ -236,9 +239,9 @@ export default class LazyLoadQueue extends LazyLoadQueueIntersector {
if(!inserted) return false;
this.intersector.observe(el.div);
if(el.wasSeen) {
/* if(el.wasSeen) {
this.processQueue(el);
} else if(!el.hasOwnProperty('wasSeen')) {
} else */if(!el.hasOwnProperty('wasSeen')) {
el.wasSeen = false;
}

2
src/components/scrollable.ts

@ -152,6 +152,8 @@ export default class Scrollable extends ScrollableBase { @@ -152,6 +152,8 @@ export default class Scrollable extends ScrollableBase {
//this.log('onScroll call', this.onScrollMeasure);
//}
//return;
if(this.onScrollMeasure || ((this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) && !this.splitUp && !this.onAdditionalScroll)) return;
this.onScrollMeasure = window.requestAnimationFrame(() => {
this.onScrollMeasure = 0;

18
src/components/sidebarLeft/index.ts

@ -13,7 +13,7 @@ import AppSearch, { SearchGroup } from "../appSearch"; @@ -13,7 +13,7 @@ import AppSearch, { SearchGroup } from "../appSearch";
import "../avatar";
import { parseMenuButtonsTo } from "../misc";
import { ScrollableX } from "../scrollable";
import SearchInput from "../searchInput";
import InputSearch from "../inputSearch";
import SidebarSlider from "../slider";
import { TransitionSlider } from "../transition";
import AppAddMembersTab from "./tabs/addMembers";
@ -81,7 +81,7 @@ export class AppSidebarLeft extends SidebarSlider { @@ -81,7 +81,7 @@ export class AppSidebarLeft extends SidebarSlider {
private backBtn: HTMLButtonElement;
private searchContainer: HTMLDivElement;
//private searchInput = document.getElementById('global-search') as HTMLInputElement;
private searchInput: SearchInput;
private inputSearch: InputSearch;
private menuEl: HTMLElement;
private buttons: {
@ -140,9 +140,9 @@ export class AppSidebarLeft extends SidebarSlider { @@ -140,9 +140,9 @@ export class AppSidebarLeft extends SidebarSlider {
//this._selectTab(0); // make first tab as default
this.searchInput = new SearchInput('Telegram Search');
this.inputSearch = new InputSearch('Telegram Search');
const sidebarHeader = this.sidebarEl.querySelector('.item-main .sidebar-header');
sidebarHeader.append(this.searchInput.container);
sidebarHeader.append(this.inputSearch.container);
this.toolsBtn = this.sidebarEl.querySelector('.sidebar-tools-button') as HTMLButtonElement;
this.backBtn = this.sidebarEl.querySelector('.sidebar-back-button') as HTMLButtonElement;
@ -161,7 +161,7 @@ export class AppSidebarLeft extends SidebarSlider { @@ -161,7 +161,7 @@ export class AppSidebarLeft extends SidebarSlider {
this.menuEl = this.toolsBtn.querySelector('.btn-menu');
this.newBtnMenu = this.sidebarEl.querySelector('#new-menu');
this.searchInput.input.addEventListener('focus', () => {
this.inputSearch.input.addEventListener('focus', () => {
this.searchGroups = {
//saved: new SearchGroup('', 'contacts'),
contacts: new SearchGroup('Chats', 'contacts'),
@ -171,8 +171,8 @@ export class AppSidebarLeft extends SidebarSlider { @@ -171,8 +171,8 @@ export class AppSidebarLeft extends SidebarSlider {
recent: new SearchGroup('Recent', 'contacts', false, 'search-group-recent')
};
this.globalSearch = new AppSearch(this.searchContainer, this.searchInput, this.searchGroups, (count) => {
if(!count && !this.searchInput.value.trim()) {
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();
@ -263,7 +263,7 @@ export class AppSidebarLeft extends SidebarSlider { @@ -263,7 +263,7 @@ export class AppSidebarLeft extends SidebarSlider {
};
let firstTime = true;
this.searchInput.input.addEventListener('focus', onFocus);
this.inputSearch.input.addEventListener('focus', onFocus);
onFocus();
this.backBtn.addEventListener('click', (e) => {
@ -339,7 +339,7 @@ export class AppSidebarLeft extends SidebarSlider { @@ -339,7 +339,7 @@ export class AppSidebarLeft extends SidebarSlider {
this.recentSearchLoaded = true;
}
if(this.searchInput.value.trim()) {
if(this.inputSearch.value.trim()) {
return;
}

10
src/components/sidebarLeft/tabs/contacts.ts

@ -5,7 +5,7 @@ import appUsersManager from "../../../lib/appManagers/appUsersManager"; @@ -5,7 +5,7 @@ import appUsersManager from "../../../lib/appManagers/appUsersManager";
import appPhotosManager from "../../../lib/appManagers/appPhotosManager";
import appSidebarLeft, { AppSidebarLeft } from "..";
import rootScope from "../../../lib/rootScope";
import SearchInput from "../../searchInput";
import InputSearch from "../../inputSearch";
// TODO: поиск по людям глобальный, если не нашло в контактах никого
@ -15,7 +15,7 @@ export default class AppContactsTab implements SliderTab { @@ -15,7 +15,7 @@ export default class AppContactsTab implements SliderTab {
private scrollable: Scrollable;
private promise: Promise<void>;
private searchInput: SearchInput;
private inputSearch: InputSearch;
init() {
this.container = document.getElementById('contacts-container');
@ -24,12 +24,12 @@ export default class AppContactsTab implements SliderTab { @@ -24,12 +24,12 @@ export default class AppContactsTab implements SliderTab {
appDialogsManager.setListClickListener(this.list);
this.scrollable = new Scrollable(this.list.parentElement);
this.searchInput = new SearchInput('Search', (value) => {
this.inputSearch = new InputSearch('Search', (value) => {
this.list.innerHTML = '';
this.openContacts(value);
});
this.container.firstElementChild.append(this.searchInput.container);
this.container.firstElementChild.append(this.inputSearch.container);
// preload contacts
// appUsersManager.getContacts();
@ -43,7 +43,7 @@ export default class AppContactsTab implements SliderTab { @@ -43,7 +43,7 @@ export default class AppContactsTab implements SliderTab {
public onCloseAfterTimeout() {
this.list.innerHTML = '';
this.searchInput.value = '';
this.inputSearch.value = '';
}
public openContacts(query?: string) {

9
src/components/sidebarRight/index.ts

@ -8,6 +8,7 @@ import AppPrivateSearchTab from "./tabs/search"; @@ -8,6 +8,7 @@ import AppPrivateSearchTab from "./tabs/search";
import AppSharedMediaTab from "./tabs/sharedMedia";
//import AppForwardTab from "./tabs/forward";
import { MOUNT_CLASS_TO } from "../../lib/mtproto/mtproto_config";
import { pause } from "../../helpers/schedulers";
export const RIGHT_COLUMN_ACTIVE_CLASSNAME = 'is-right-column-shown';
@ -111,14 +112,10 @@ export class AppSidebarRight extends SidebarSlider { @@ -111,14 +112,10 @@ export class AppSidebarRight extends SidebarSlider {
//if(mediaSizes.isMobile) {
//appImManager._selectTab(active ? 1 : 2);
appImManager.selectTab(active ? 1 : 2);
return new Promise(resolve => {
setTimeout(resolve, mediaSizes.isMobile ? 250 : 200); // delay of slider animation
});
return pause(mediaSizes.isMobile ? 250 : 200); // delay of slider animation
//}
return new Promise(resolve => {
setTimeout(resolve, 200); // delay for third column open
});
return pause(200); // delay for third column open
//return Promise.resolve();
/* return new Promise((resolve, reject) => {

14
src/components/sidebarRight/tabs/gifs.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { SliderTab } from "../../slider";
import SearchInput from "../../searchInput";
import InputSearch from "../../inputSearch";
import Scrollable from "../../scrollable";
import animationIntersector from "../../animationIntersector";
import appSidebarRight, { AppSidebarRight } from "..";
@ -18,7 +18,7 @@ export default class AppGifsTab implements SliderTab { @@ -18,7 +18,7 @@ export default class AppGifsTab implements SliderTab {
private contentDiv = this.container.querySelector('.sidebar-content') as HTMLDivElement;
private backBtn = this.container.querySelector('.sidebar-close-button') as HTMLButtonElement;
//private input = this.container.querySelector('#stickers-search') as HTMLInputElement;
private searchInput: SearchInput;
private inputSearch: InputSearch;
private gifsDiv = this.contentDiv.firstElementChild as HTMLDivElement;
private scrollable: Scrollable;
@ -35,14 +35,14 @@ export default class AppGifsTab implements SliderTab { @@ -35,14 +35,14 @@ export default class AppGifsTab implements SliderTab {
this.masonry = new GifsMasonry(this.gifsDiv, ANIMATIONGROUP, this.scrollable);
this.searchInput = new SearchInput('Search GIFs', (value) => {
this.inputSearch = new InputSearch('Search GIFs', (value) => {
this.reset();
this.search(value);
});
this.gifsDiv.addEventListener('click', this.onGifsClick);
this.backBtn.parentElement.append(this.searchInput.container);
this.backBtn.parentElement.append(this.inputSearch.container);
}
onGifsClick = (e: MouseEvent) => {
@ -66,7 +66,7 @@ export default class AppGifsTab implements SliderTab { @@ -66,7 +66,7 @@ export default class AppGifsTab implements SliderTab {
public onCloseAfterTimeout() {
this.reset();
this.gifsDiv.innerHTML = '';
this.searchInput.value = '';
this.inputSearch.value = '';
animationIntersector.checkAnimations(undefined, ANIMATIONGROUP);
}
@ -86,7 +86,7 @@ export default class AppGifsTab implements SliderTab { @@ -86,7 +86,7 @@ export default class AppGifsTab implements SliderTab {
this.reset();
this.scrollable.onScrolledBottom = () => {
this.search(this.searchInput.value, false);
this.search(this.inputSearch.value, false);
};
});
}
@ -102,7 +102,7 @@ export default class AppGifsTab implements SliderTab { @@ -102,7 +102,7 @@ export default class AppGifsTab implements SliderTab {
this.searchPromise = appInlineBotsManager.getInlineResults(0, this.gifBotPeerId, query, this.nextOffset);
const { results, next_offset } = await this.searchPromise;
if(this.searchInput.value != query) {
if(this.inputSearch.value != query) {
return;
}

10
src/components/sidebarRight/tabs/search.ts

@ -1,13 +1,13 @@ @@ -1,13 +1,13 @@
import appSidebarRight, { AppSidebarRight } from "..";
import AppSearch, { SearchGroup } from "../../appSearch";
import SearchInput from "../../searchInput";
import InputSearch from "../../inputSearch";
import { SliderTab } from "../../slider";
export default class AppPrivateSearchTab implements SliderTab {
public container: HTMLElement;
public closeBtn: HTMLElement;
private searchInput: SearchInput;
private inputSearch: InputSearch;
private appSearch: AppSearch;
private peerId = 0;
@ -24,9 +24,9 @@ export default class AppPrivateSearchTab implements SliderTab { @@ -24,9 +24,9 @@ export default class AppPrivateSearchTab implements SliderTab {
public init() {
this.container = document.getElementById('search-private-container');
this.closeBtn = this.container.querySelector('.sidebar-close-button');
this.searchInput = new SearchInput('Search');
this.closeBtn.parentElement.append(this.searchInput.container);
this.appSearch = new AppSearch(this.container.querySelector('.chatlist-container'), this.searchInput, {
this.inputSearch = new InputSearch('Search');
this.closeBtn.parentElement.append(this.inputSearch.container);
this.appSearch = new AppSearch(this.container.querySelector('.chatlist-container'), this.inputSearch, {
messages: new SearchGroup('Private Search', 'messages')
});
}

14
src/components/sidebarRight/tabs/stickers.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import { SliderTab } from "../../slider";
import SearchInput from "../../searchInput";
import InputSearch from "../../inputSearch";
import Scrollable from "../../scrollable";
import LazyLoadQueue from "../../lazyLoadQueue";
import { findUpClassName } from "../../../helpers/dom";
@ -17,7 +17,7 @@ export default class AppStickersTab implements SliderTab { @@ -17,7 +17,7 @@ export default class AppStickersTab implements SliderTab {
private contentDiv = this.container.querySelector('.sidebar-content') as HTMLDivElement;
private backBtn = this.container.querySelector('.sidebar-close-button') as HTMLButtonElement;
//private input = this.container.querySelector('#stickers-search') as HTMLInputElement;
private searchInput: SearchInput;
private inputSearch: InputSearch;
private setsDiv = this.contentDiv.firstElementChild as HTMLDivElement;
private scrollable: Scrollable;
private lazyLoadQueue: LazyLoadQueue;
@ -27,11 +27,11 @@ export default class AppStickersTab implements SliderTab { @@ -27,11 +27,11 @@ export default class AppStickersTab implements SliderTab {
this.lazyLoadQueue = new LazyLoadQueue();
this.searchInput = new SearchInput('Search Stickers', (value) => {
this.inputSearch = new InputSearch('Search Stickers', (value) => {
this.search(value);
});
this.backBtn.parentElement.append(this.searchInput.container);
this.backBtn.parentElement.append(this.inputSearch.container);
this.setsDiv.addEventListener('click', (e) => {
const sticker = findUpClassName(e.target, 'sticker-set-sticker');
@ -76,7 +76,7 @@ export default class AppStickersTab implements SliderTab { @@ -76,7 +76,7 @@ export default class AppStickersTab implements SliderTab {
public onCloseAfterTimeout() {
this.setsDiv.innerHTML = '';
this.searchInput.value = '';
this.inputSearch.value = '';
animationIntersector.checkAnimations(undefined, 'STICKERS-SEARCH');
}
@ -188,7 +188,7 @@ export default class AppStickersTab implements SliderTab { @@ -188,7 +188,7 @@ export default class AppStickersTab implements SliderTab {
public renderFeatured() {
return appStickersManager.getFeaturedStickers().then(coveredSets => {
if(this.searchInput.value) {
if(this.inputSearch.value) {
return;
}
@ -225,7 +225,7 @@ export default class AppStickersTab implements SliderTab { @@ -225,7 +225,7 @@ export default class AppStickersTab implements SliderTab {
}
return appStickersManager.searchStickerSets(query, false).then(coveredSets => {
if(this.searchInput.value != query) {
if(this.inputSearch.value != query) {
return;
}

4
src/components/wrappers.ts

@ -572,7 +572,7 @@ export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withT @@ -572,7 +572,7 @@ export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withT
});
};
return cacheContext.downloaded || !lazyLoadQueue ? load() : (lazyLoadQueue.push({div: container, load: load, wasSeen: true}), Promise.resolve());
return cacheContext.downloaded || !lazyLoadQueue ? load() : (lazyLoadQueue.push({div: container, load/* : load, wasSeen: true */}), Promise.resolve());
}
export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, onlyThumb, emoji, width, height, withThumb, loop}: {
@ -813,7 +813,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -813,7 +813,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
}
};
return lazyLoadQueue && (!doc.downloaded || stickerType == 2) ? (lazyLoadQueue.push({div, load, wasSeen: group == 'chat' && stickerType != 2}), Promise.resolve()) : load();
return lazyLoadQueue && (!doc.downloaded || stickerType == 2) ? (lazyLoadQueue.push({div, load/* , wasSeen: group == 'chat' && stickerType != 2 */}), Promise.resolve()) : load();
}
export function wrapReply(title: string, subtitle: string, message?: any) {

125
src/helpers/schedulers.ts

@ -0,0 +1,125 @@ @@ -0,0 +1,125 @@
// * Jolly Cobra's schedulers
import { AnyToVoidFunction } from "../types";
//type Scheduler = typeof requestAnimationFrame | typeof onTickEnd | typeof runNow;
export function debounce<F extends AnyToVoidFunction>(
fn: F,
ms: number,
shouldRunFirst = true,
shouldRunLast = true,
) {
let waitingTimeout: number | null = null;
return (...args: Parameters<F>) => {
if(waitingTimeout) {
clearTimeout(waitingTimeout);
waitingTimeout = null;
} else if(shouldRunFirst) {
// @ts-ignore
fn(...args);
}
waitingTimeout = window.setTimeout(() => {
if(shouldRunLast) {
// @ts-ignore
fn(...args);
}
waitingTimeout = null;
}, ms);
};
}
/* export function throttle<F extends AnyToVoidFunction>(
fn: F,
ms: number,
shouldRunFirst = true,
) {
let interval: number | null = null;
let isPending: boolean;
let args: Parameters<F>;
return (..._args: Parameters<F>) => {
isPending = true;
args = _args;
if (!interval) {
if (shouldRunFirst) {
isPending = false;
// @ts-ignore
fn(...args);
}
interval = window.setInterval(() => {
if (!isPending) {
window.clearInterval(interval!);
interval = null;
return;
}
isPending = false;
// @ts-ignore
fn(...args);
}, ms);
}
};
} */
/* export function throttleWithRaf<F extends AnyToVoidFunction>(fn: F) {
return throttleWith(fastRaf, fn);
}
export function throttleWithTickEnd<F extends AnyToVoidFunction>(fn: F) {
return throttleWith(onTickEnd, fn);
}
export function throttleWithNow<F extends AnyToVoidFunction>(fn: F) {
return throttleWith(runNow, fn);
}
export function throttleWith<F extends AnyToVoidFunction>(schedulerFn: Scheduler, fn: F) {
let waiting = false;
let args: Parameters<F>;
return (..._args: Parameters<F>) => {
args = _args;
if (!waiting) {
waiting = true;
schedulerFn(() => {
waiting = false;
// @ts-ignore
fn(...args);
});
}
};
}
export function onTickEnd(cb: NoneToVoidFunction) {
Promise.resolve().then(cb);
}
function runNow(fn: NoneToVoidFunction) {
fn();
} */
export const pause = (ms: number) => new Promise((resolve) => {
setTimeout(resolve, ms);
});
/* let fastRafCallbacks: NoneToVoidFunction[] | undefined;
export function fastRaf(callback: NoneToVoidFunction) {
if (!fastRafCallbacks) {
fastRafCallbacks = [callback];
requestAnimationFrame(() => {
const currentCallbacks = fastRafCallbacks!;
fastRafCallbacks = undefined;
currentCallbacks.forEach((cb) => cb());
});
} else {
fastRafCallbacks.push(callback);
}
} */

2
src/index.hbs

@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
<meta charset="utf-8">
<title>Telegram Web</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<link rel="apple-touch-icon" sizes="180x180" href="assets/img/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="assets/img/favicon-32x32.png">

4
src/lib/mtproto/apiFileManager.ts

@ -210,9 +210,9 @@ export class ApiFileManager { @@ -210,9 +210,9 @@ export class ApiFileManager {
this.log('downloadFile', fileName, size, location, options.mimeType, process);
if(options.queueId) {
/* if(options.queueId) {
this.log.error('downloadFile queueId:', fileName, options.queueId);
}
} */
if(cachedPromise) {
//this.log('downloadFile cachedPromise');

12
src/lib/mtproto/tl_utils.ts

@ -699,7 +699,17 @@ class TLDeserialization { @@ -699,7 +699,17 @@ class TLDeserialization {
}
if(!constructorData) {
throw new Error('Constructor not found: ' + constructor + ' ' + this.fetchInt() + ' ' + this.fetchInt() + ' ' + field);
console.error('Constructor not found:', constructor);
let int1: number, int2: number;
try {
int1 = this.fetchInt(field);
int2 = this.fetchInt(field);
} catch(err) {
}
throw new Error('Constructor not found: ' + constructor + ' ' + int1 + ' ' + int2 + ' ' + field);
}
}

3
src/pages/pageSignQR.ts

@ -8,6 +8,7 @@ import { App } from '../lib/mtproto/mtproto_config'; @@ -8,6 +8,7 @@ import { App } from '../lib/mtproto/mtproto_config';
import serverTimeManager from '../lib/mtproto/serverTimeManager';
import { AuthAuthorization, AuthLoginToken } from '../layer';
import { bytesCmp, bytesToBase64 } from '../helpers/bytes';
import { pause } from '../helpers/schedulers';
let onFirstMount = async() => {
const pageElement = page.pageEl;
@ -102,7 +103,7 @@ let onFirstMount = async() => { @@ -102,7 +103,7 @@ let onFirstMount = async() => {
let timestamp = Date.now() / 1000;
let diff = loginToken.expires - timestamp - serverTimeManager.serverTimeOffset;
await new Promise((resolve, reject) => setTimeout(resolve, diff > 5 ? 5e3 : 1e3 * diff | 0));
await pause(diff > 5 ? 5e3 : 1e3 * diff | 0);
} catch(err) {
switch(err.type) {
case 'SESSION_PASSWORD_NEEDED':

29
src/scss/partials/_chatPinned.scss

@ -337,6 +337,35 @@ @@ -337,6 +337,35 @@
}
}
.animated-super-row {
--translateY: 16px;
}
.pinned-message-media {
--translateY: 32px;
}
/* .animated-super-row.is-hiding {
&.from-top {
transform: translateY(-16px);
}
&.from-bottom {
transform: translateY(16px);
}
}
.pinned-message-media.is-hiding {
&.from-top {
transform: translateY(-32px);
}
&.from-bottom {
transform: translateY(32px);
}
} */
&.hide ~ .tgico-pinlist, &:not(.is-many) ~ .tgico-pinlist {
display: none;
}

67
src/scss/partials/_chatlist.scss

@ -13,73 +13,6 @@ @@ -13,73 +13,6 @@
}
}
.input-search {
position: relative;
width: 100%;
//Vozmojno nado budet vernut margin-left: 22px;, tak kak eto vrode v levom bare tak po verstke, a v pravom bare dlya mobili nado 16, gde stiker seti
margin-left: 22px;
margin-right: 4px;
@include respond-to(handhelds) {
margin-left: 16px;
}
input {
--border-width: 1px;
background-color: var(--color-gray-hover);
height: 40px;
border-radius: 22px;
border: var(--border-width) solid transparent;
box-sizing: border-box;
padding: 0px calc(1.5rem - var(--border-width)) 0 calc(42px - var(--border-width));
transition: background-color .15s ease-in-out, border-color .15s ease-in-out;
width: 100%;
font-size: 16px;
&:hover {
border-color: var(--color-gray);
}
&:focus {
--border-width: 2px;
background-color: transparent;
border-color: $button-primary-background;
& + .tgico {
color: $button-primary-background;
opacity: 1;
}
}
}
.tgico {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
text-align: center;
font-size: 24px;
color: $color-gray;
opacity: .6;
transition: all .15s ease-out;
&:before {
vertical-align: middle;
}
}
.tgico-close {
left: auto;
right: 0px;
top: 48%;
}
//input.is-empty ~ .tgico-close {
input:placeholder-shown ~ .tgico-close {
display: none;
}
}
ul {
margin: 0;
//padding: 0 .5rem;

83
src/scss/partials/_input.scss

@ -47,15 +47,17 @@ @@ -47,15 +47,17 @@
}
input, &-input {
--height: 54px;
--padding: 1rem;
--border-width: 1px;
--border-width-top: 2px;
border: var(--border-width) solid #DADCE0;
border-radius: $border-radius-medium;
//padding: 0 1rem;
padding: calc(1rem - var(--border-width-top)) calc(1rem - var(--border-width));
padding: calc(var(--padding) - var(--border-width-top)) calc(var(--padding) - var(--border-width));
box-sizing: border-box;
width: 100%;
min-height: 54px;
min-height: var(--height);
transition: .2s border-color;
position: relative;
z-index: 1;
@ -126,7 +128,7 @@ @@ -126,7 +128,7 @@
transform: none;
padding: 0 5px;
left: .75rem;
font-size: 0.75rem!important;
font-size: .75rem!important;
//color: #666;
opacity: 1;
}
@ -143,11 +145,11 @@ @@ -143,11 +145,11 @@
}
:-ms-input-placeholder { /* Internet Explorer 10-11 */
color: #a2acb4;
color: #909192;
}
::-ms-input-placeholder { /* Microsoft Edge */
color: #a2acb4;
color: #909192;
}
input:focus, button:focus {
@ -181,3 +183,74 @@ input:focus, button:focus { @@ -181,3 +183,74 @@ input:focus, button:focus {
transform: translateX(0);
}
}
.input-search {
position: relative;
width: 100%;
//Vozmojno nado budet vernut margin-left: 22px;, tak kak eto vrode v levom bare tak po verstke, a v pravom bare dlya mobili nado 16, gde stiker seti
margin-left: 22px;
margin-right: 4px;
overflow: hidden;
@include respond-to(handhelds) {
margin-left: 16px;
}
&-input {
--height: 40px;
background-color: var(--color-gray-hover);
padding: 0px calc(42px - var(--border-width));
height: var(--height);
max-height: var(--height);
//line-height: calc(var(--height) + 2px - var(--border-width) * 2);
border-radius: 22px;
transition: background-color .2s ease-in-out, border-color .2s ease-in-out;
border-color: transparent;
&:hover {
border-color: var(--color-gray);
}
&:focus {
--border-width: 2px;
background-color: transparent;
border-color: $button-primary-background;
& + .tgico {
color: $button-primary-background;
opacity: 1;
}
}
/* &:empty:before {
color: #909192 !important;
} */
/* &:empty ~ .tgico-close, */&:placeholder-shown ~ .tgico-close {
display: none;
}
}
.tgico {
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
text-align: center;
font-size: 24px;
color: $color-gray;
opacity: .6;
transition: all .2s ease-out;
&:before {
vertical-align: middle;
}
}
.tgico-close {
left: auto;
right: 0px;
top: 48%;
z-index: 1;
}
}

13
src/scss/style.scss

@ -870,6 +870,7 @@ img.emoji { @@ -870,6 +870,7 @@ img.emoji {
.animated-super {
&-row {
--translateY: 100%;
position: absolute;
left: 0;
top: 0;
@ -877,15 +878,23 @@ img.emoji { @@ -877,15 +878,23 @@ img.emoji {
bottom: 0;
transition: transform var(--pm-transition), opacity var(--pm-transition);
/* &:not(.is-hiding) {
transform: none !important;
} */
&.is-hiding {
opacity: 0;
&.from-top {
transform: translateY(-100%);
transform: translate3d(0, calc(var(--translateY) * -1), 0);
//transform: translateY(calc(var(--translateY) * -1));
//transform: translateY(-100%);
}
&.from-bottom {
transform: translateY(100%);
transform: translate3d(0, var(--translateY), 0);
//transform: translateY(var(--translateY));
//transform: translateY(100%);
}
/* &.backwards {

6
src/types.d.ts vendored

@ -35,6 +35,12 @@ export type Modify<T, R> = Omit<T, keyof R> & R; @@ -35,6 +35,12 @@ export type Modify<T, R> = Omit<T, keyof R> & R;
export type ArgumentTypes<F extends Function> = F extends (...args: infer A) => any ? A : never;
export type AnyLiteral = Record<string, any>;
export type AnyClass = new (...args: any[]) => any;
export type AnyFunction = (...args: any) => any;
export type AnyToVoidFunction = (...args: any) => void;
export type NoneToVoidFunction = () => void;
export type AuthState = AuthState.signIn | AuthState.authCode | AuthState.password | AuthState.signUp | AuthState.signedIn;
export namespace AuthState {
export type signIn = {

Loading…
Cancel
Save