Browse Source

Fix moving avatars after chat jump

Handle instant peer changing correctly
Display unread badge only in chats
Delay opening on ESG hover
Lazy load sticker sets thumbs
Fix onchanging profile info flick
Hide shared media menu tabs
Scroll on click to shared media tab
Fix media tabs scroll position
Fix voice messages playback in search on iOS
Hover & play on waveform
Better input fields border animation
Don't lock scroll on horizontal scrollables if unnecessary
Restrict editing & deleting outgoing messages
Display avatars in notifications
Open chat on notification click
Don't close country selector on scrolling
Select single country by enter
Fix jumping text in context menus
Changed preloader color
master
Eduard Kuzmenko 4 years ago
parent
commit
198eea41ee
  1. 4
      README.md
  2. 65
      src/components/appSearchSuper..ts
  3. 5
      src/components/audio.ts
  4. 16
      src/components/chat/bubbles.ts
  5. 2
      src/components/chat/chat.ts
  6. 9
      src/components/chat/input.ts
  7. 36
      src/components/emoticonsDropdown/index.ts
  8. 13
      src/components/emoticonsDropdown/tabs/stickers.ts
  9. 9
      src/components/horizontalMenu.ts
  10. 6
      src/components/inputField.ts
  11. 2
      src/components/inputSearch.ts
  12. 4
      src/components/scrollable.ts
  13. 4
      src/components/sidebarRight/tabs/sharedMedia.ts
  14. 4
      src/config/app.ts
  15. 9
      src/lang.ts
  16. 4
      src/lib/appManagers/appChatsManager.ts
  17. 1
      src/lib/appManagers/appDialogsManager.ts
  18. 5
      src/lib/appManagers/appImManager.ts
  19. 70
      src/lib/appManagers/appMessagesManager.ts
  20. 10
      src/lib/appManagers/appNotificationsManager.ts
  21. 28
      src/lib/appManagers/appProfileManager.ts
  22. 19
      src/lib/mtproto/networker.ts
  23. 5
      src/lib/mtproto/serverTimeManager.ts
  24. 2
      src/lib/rootScope.ts
  25. 3
      src/pages/pageAuthCode.ts
  26. 63
      src/pages/pageSignIn.ts
  27. 4
      src/scss/partials/_audio.scss
  28. 4
      src/scss/partials/_button.scss
  29. 12
      src/scss/partials/_chat.scss
  30. 4
      src/scss/partials/_chatBubble.scss
  31. 50
      src/scss/partials/_input.scss
  32. 2
      src/scss/partials/_preloader.scss
  33. 5
      src/scss/partials/_rightSidebar.scss
  34. 11
      src/scss/partials/popups/_createPoll.scss

4
README.md

@ -42,9 +42,9 @@ Source maps are included in production build for your convenience.
Should be applied like that: http://localhost:8080/?test=1 Should be applied like that: http://localhost:8080/?test=1
### Troubleshooting ### Troubleshooting & Suggesting
If you find an issue with this app, let Telegram know using the [Suggestions Platform](https://bugs.telegram.org/c/4002). If you find an issue with this app or wish something to be added, let Telegram know using the [Suggestions Platform](https://bugs.telegram.org/c/4002).
### Licensing ### Licensing

65
src/components/appSearchSuper..ts

@ -70,10 +70,10 @@ export default class AppSearchSuper {
public tabs: {[t in SearchSuperType]: HTMLDivElement} = {} as any; public tabs: {[t in SearchSuperType]: HTMLDivElement} = {} as any;
public mediaTab: SearchSuperMediaTab; public mediaTab: SearchSuperMediaTab;
public tabSelected: HTMLElement;
public container: HTMLElement; public container: HTMLElement;
public nav: HTMLElement; public nav: HTMLElement;
private navScrollableContainer: HTMLDivElement;
private tabsContainer: HTMLElement; private tabsContainer: HTMLElement;
private tabsMenu: HTMLElement; private tabsMenu: HTMLElement;
private prevTabId = -1; private prevTabId = -1;
@ -112,6 +112,8 @@ export default class AppSearchSuper {
private membersList: SortedUserList; private membersList: SortedUserList;
private skipScroll: boolean;
// * arguments // * arguments
public mediaTabs: SearchSuperMediaTab[]; public mediaTabs: SearchSuperMediaTab[];
public scrollable: Scrollable; public scrollable: Scrollable;
@ -128,7 +130,7 @@ export default class AppSearchSuper {
this.container = document.createElement('div'); this.container = document.createElement('div');
this.container.classList.add('search-super'); this.container.classList.add('search-super');
const navScrollableContainer = document.createElement('div'); const navScrollableContainer = this.navScrollableContainer = document.createElement('div');
navScrollableContainer.classList.add('search-super-tabs-scrollable', 'menu-horizontal-scrollable', 'sticky'); navScrollableContainer.classList.add('search-super-tabs-scrollable', 'menu-horizontal-scrollable', 'sticky');
const navScrollable = new ScrollableX(navScrollableContainer); const navScrollable = new ScrollableX(navScrollableContainer);
@ -185,48 +187,67 @@ export default class AppSearchSuper {
this.searchGroupMedia = new SearchGroup('', 'messages', true); this.searchGroupMedia = new SearchGroup('', 'messages', true);
this.scrollable.onScrolledBottom = () => { this.scrollable.onScrolledBottom = () => {
if(this.tabSelected && this.tabSelected.childElementCount/* && false */) { if(this.mediaTab.contentTab && this.mediaTab.contentTab.childElementCount/* && false */) {
//this.log('onScrolledBottom will load media'); //this.log('onScrolledBottom will load media');
this.load(true); this.load(true);
} }
}; };
//this.scroll.attachSentinels(undefined, 400); //this.scroll.attachSentinels(undefined, 400);
this.selectTab = horizontalMenu(this.tabsMenu, this.tabsContainer, (id, tabContent) => { this.selectTab = horizontalMenu(this.tabsMenu, this.tabsContainer, (id, tabContent, animate) => {
if(this.prevTabId === id) return; if(this.prevTabId === id && !this.skipScroll) {
this.scrollable.scrollIntoViewNew(this.container, 'start');
return;
}
const newMediaTab = this.mediaTabs[id];
if(this.onChangeTab) {
this.onChangeTab(newMediaTab);
}
const fromMediaTab = this.mediaTab;
this.mediaTab = newMediaTab;
if(this.prevTabId !== -1) { if(this.prevTabId !== -1 && animate) {
this.onTransitionStart(); this.onTransitionStart();
} }
this.mediaTab.scroll = {scrollTop: this.scrollable.scrollTop, scrollHeight: this.scrollable.scrollHeight}; if(this.skipScroll) {
this.skipScroll = false;
} else {
const offsetTop = this.container.offsetTop;
let scrollTop = this.scrollable.scrollTop;
if(scrollTop < offsetTop) {
this.scrollable.scrollIntoViewNew(this.container, 'start');
scrollTop = offsetTop;
}
const newMediaTab = this.mediaTabs[id]; fromMediaTab.scroll = {scrollTop: scrollTop, scrollHeight: this.scrollable.scrollHeight};
this.tabSelected = tabContent.firstElementChild as HTMLDivElement;
if(newMediaTab.scroll === undefined) { if(newMediaTab.scroll === undefined) {
const rect = this.container.getBoundingClientRect(); const rect = this.container.getBoundingClientRect();
const rect2 = this.container.parentElement.getBoundingClientRect(); const rect2 = this.container.parentElement.getBoundingClientRect();
const diff = rect.y - rect2.y; const diff = rect.y - rect2.y;
if(this.scrollable.scrollTop > diff) { if(scrollTop > diff) {
newMediaTab.scroll = {scrollTop: diff, scrollHeight: 0}; newMediaTab.scroll = {scrollTop: diff, scrollHeight: 0};
} }
} }
if(newMediaTab.scroll) { if(newMediaTab.scroll) {
const diff = this.mediaTab.scroll.scrollTop - newMediaTab.scroll.scrollTop; const diff = fromMediaTab.scroll.scrollTop - newMediaTab.scroll.scrollTop;
//console.log('what you gonna do', this.goingHard, diff); //console.log('what you gonna do', this.goingHard, diff);
//this.scrollable.scrollTop = scrollTop;
if(diff/* && diff < 0 */) { if(diff/* && diff < 0 */) {
this.tabSelected.style.transform = `translateY(${diff}px)`; /* if(diff > -(fromMediaTab.contentTab.scrollHeight + this.nav.scrollHeight)) {
fromMediaTab.contentTab.style.transform = `translateY(${diff}px)`;
this.scrollable.scrollTop = scrollTop - diff;
} else { */
newMediaTab.contentTab.style.transform = `translateY(${diff}px)`;
//}
} }
} }
this.mediaTab = newMediaTab;
if(this.onChangeTab) {
this.onChangeTab(this.mediaTab);
} }
/* if(this.prevTabId !== -1 && nav.offsetTop) { /* if(this.prevTabId !== -1 && nav.offsetTop) {
@ -236,7 +257,7 @@ export default class AppSearchSuper {
/* this.log('setVirtualContainer', id, this.sharedMediaSelected, this.sharedMediaSelected.childElementCount); /* this.log('setVirtualContainer', id, this.sharedMediaSelected, this.sharedMediaSelected.childElementCount);
this.scroll.setVirtualContainer(this.sharedMediaSelected); */ this.scroll.setVirtualContainer(this.sharedMediaSelected); */
if(this.prevTabId !== -1 && !this.tabSelected.childElementCount) { // quick brown fix if(this.prevTabId !== -1 && !newMediaTab.contentTab.childElementCount) { // quick brown fix
//this.contentContainer.classList.remove('loaded'); //this.contentContainer.classList.remove('loaded');
this.load(true); this.load(true);
} }
@ -247,7 +268,7 @@ export default class AppSearchSuper {
//console.log('what y', this.tabSelected.style.transform); //console.log('what y', this.tabSelected.style.transform);
if(this.mediaTab.scroll !== undefined) { if(this.mediaTab.scroll !== undefined) {
this.tabSelected.style.transform = ''; this.mediaTab.contentTab.style.transform = '';
this.scrollable.scrollTop = this.mediaTab.scroll.scrollTop; this.scrollable.scrollTop = this.mediaTab.scroll.scrollTop;
} }
@ -1097,6 +1118,7 @@ export default class AppSearchSuper {
} }
let firstMediaTab: SearchSuperMediaTab; let firstMediaTab: SearchSuperMediaTab;
let count = 0;
mediaTabs.forEach(mediaTab => { mediaTabs.forEach(mediaTab => {
const counter = counters.find(c => c.filter._ === mediaTab.inputFilter); const counter = counters.find(c => c.filter._ === mediaTab.inputFilter);
@ -1107,6 +1129,8 @@ export default class AppSearchSuper {
if(counter.count && firstMediaTab === undefined) { if(counter.count && firstMediaTab === undefined) {
firstMediaTab = mediaTab; firstMediaTab = mediaTab;
} }
if(counter.count) ++count;
}); });
const membersTab = this.mediaTabsMap.get('members'); const membersTab = this.mediaTabsMap.get('members');
@ -1120,8 +1144,11 @@ export default class AppSearchSuper {
this.container.classList.toggle('hide', !firstMediaTab); this.container.classList.toggle('hide', !firstMediaTab);
this.container.parentElement.classList.toggle('search-empty', !firstMediaTab); this.container.parentElement.classList.toggle('search-empty', !firstMediaTab);
if(firstMediaTab) { if(firstMediaTab) {
this.skipScroll = true;
this.selectTab(this.mediaTabs.indexOf(firstMediaTab), false); this.selectTab(this.mediaTabs.indexOf(firstMediaTab), false);
firstMediaTab.menuTab.classList.add('active'); firstMediaTab.menuTab.classList.add('active');
this.navScrollableContainer.classList.toggle('hide', count <= 1);
} }
} }

5
src/components/audio.ts

@ -217,9 +217,10 @@ function wrapVoiceMessage(audioEl: AudioElement) {
e.preventDefault(); e.preventDefault();
if(!audio.paused) { if(!audio.paused) {
audio.pause(); audio.pause();
}
scrub(e); scrub(e);
mousedown = true; mousedown = true;
}
}); });
progress.addEventListener('mouseup', (e) => { progress.addEventListener('mouseup', (e) => {
if (mousemove && mousedown) { if (mousemove && mousedown) {
@ -437,7 +438,7 @@ export default class AudioElement extends HTMLElement {
const getDownloadPromise = () => appDocsManager.downloadDoc(doc); const getDownloadPromise = () => appDocsManager.downloadDoc(doc);
if(isVoice) { if(isRealVoice) {
if(!preloader) { if(!preloader) {
preloader = new ProgressivePreloader({ preloader = new ProgressivePreloader({
cancelable: true cancelable: true

16
src/components/chat/bubbles.ts

@ -168,6 +168,7 @@ export default class ChatBubbles {
const message = this.chat.getMessage(mid); const message = this.chat.getMessage(mid);
if(+bubble.dataset.timestamp >= (message.date + serverTimeManager.serverTimeOffset - 1)) { if(+bubble.dataset.timestamp >= (message.date + serverTimeManager.serverTimeOffset - 1)) {
this.bubbleGroups.addBubble(bubble, message, false);
return; return;
} }
@ -1025,6 +1026,7 @@ export default class ChatBubbles {
// if scroll down after search // if scroll down after search
if(history.indexOf(historyStorage.maxId) !== -1) { if(history.indexOf(historyStorage.maxId) !== -1) {
this.scrollable.loadedAll.bottom = true;
return; return;
} }
@ -1414,6 +1416,10 @@ export default class ChatBubbles {
const samePeer = this.peerId === peerId; const samePeer = this.peerId === peerId;
/* if(samePeer && this.chat.setPeerPromise) {
return {cached: true, promise: this.chat.setPeerPromise};
} */
const historyStorage = this.appMessagesManager.getHistoryStorage(peerId, this.chat.threadId); const historyStorage = this.appMessagesManager.getHistoryStorage(peerId, this.chat.threadId);
let topMessage = this.chat.type === 'pinned' ? this.appMessagesManager.pinnedMessages[peerId].maxId : historyStorage.maxId ?? 0; let topMessage = this.chat.type === 'pinned' ? this.appMessagesManager.pinnedMessages[peerId].maxId : historyStorage.maxId ?? 0;
const isTarget = lastMsgId !== undefined; const isTarget = lastMsgId !== undefined;
@ -1497,9 +1503,15 @@ export default class ChatBubbles {
this.isFirstLoad = true; this.isFirstLoad = true;
} }
const oldChatInner = this.chatInner;
this.cleanup(); this.cleanup();
this.chatInner = document.createElement('div'); this.chatInner = document.createElement('div');
if(samePeer) {
this.chatInner.className = oldChatInner.className;
this.chatInner.classList.remove('disable-hover', 'is-scrolling');
} else {
this.chatInner.classList.add('bubbles-inner'); this.chatInner.classList.add('bubbles-inner');
}
this.lazyLoadQueue.lock(); this.lazyLoadQueue.lock();
@ -1590,6 +1602,8 @@ export default class ChatBubbles {
this.scrollable.scrollTop = 99999; this.scrollable.scrollTop = 99999;
} }
this.onScroll();
this.chat.dispatchEvent('setPeer', lastMsgId, !isJump); this.chat.dispatchEvent('setPeer', lastMsgId, !isJump);
// warning // warning
@ -1718,7 +1732,7 @@ export default class ChatBubbles {
public setBubblePosition(bubble: HTMLElement, message: any, reverse: boolean) { public setBubblePosition(bubble: HTMLElement, message: any, reverse: boolean) {
const dateMessage = this.getDateContainerByMessage(message, reverse); const dateMessage = this.getDateContainerByMessage(message, reverse);
if(this.chat.type === 'scheduled' || this.chat.type === 'pinned') { if(this.chat.type === 'scheduled' || this.chat.type === 'pinned' || true) {
const offset = this.stickyIntersector ? 2 : 1; const offset = this.stickyIntersector ? 2 : 1;
let children = Array.from(dateMessage.container.children).slice(offset) as HTMLElement[]; let children = Array.from(dateMessage.container.children).slice(offset) as HTMLElement[];
let i = 0, foundMidOnSameTimestamp = 0; let i = 0, foundMidOnSameTimestamp = 0;

2
src/components/chat/chat.ts

@ -206,6 +206,8 @@ export default class Chat extends EventListenerBase<{
if(!samePeer) { if(!samePeer) {
rootScope.broadcast('peer_changing', this); rootScope.broadcast('peer_changing', this);
this.peerId = peerId; this.peerId = peerId;
} else if(this.setPeerPromise) {
return;
} }
//console.time('appImManager setPeer'); //console.time('appImManager setPeer');

9
src/components/chat/input.ts

@ -157,9 +157,6 @@ export default class ChatInput {
this.chatInput.append(this.inputContainer); this.chatInput.append(this.inputContainer);
this.goDownBtn = Button('bubbles-go-down btn-corner btn-circle z-depth-1 hide', {icon: 'arrow_down'}); this.goDownBtn = Button('bubbles-go-down btn-corner btn-circle z-depth-1 hide', {icon: 'arrow_down'});
this.goDownUnreadBadge = document.createElement('span');
this.goDownUnreadBadge.classList.add('badge', 'badge-24', 'badge-primary');
this.goDownBtn.append(this.goDownUnreadBadge);
this.inputContainer.append(this.goDownBtn); this.inputContainer.append(this.goDownBtn);
attachClickEvent(this.goDownBtn, (e) => { attachClickEvent(this.goDownBtn, (e) => {
@ -257,6 +254,10 @@ export default class ChatInput {
this.inputMessageContainer.classList.add('input-message-container'); this.inputMessageContainer.classList.add('input-message-container');
if(this.chat.type === 'chat') { if(this.chat.type === 'chat') {
this.goDownUnreadBadge = document.createElement('span');
this.goDownUnreadBadge.classList.add('badge', 'badge-24', 'badge-primary');
this.goDownBtn.append(this.goDownUnreadBadge);
this.btnScheduled = ButtonIcon('scheduled', {noRipple: true}); this.btnScheduled = ButtonIcon('scheduled', {noRipple: true});
this.btnScheduled.classList.add('btn-scheduled', 'hide'); this.btnScheduled.classList.add('btn-scheduled', 'hide');
@ -376,7 +377,7 @@ export default class ChatInput {
this.inputContainer.append(this.btnCancelRecord, this.btnSendContainer); this.inputContainer.append(this.btnCancelRecord, this.btnSendContainer);
emoticonsDropdown.attachButtonListener(this.btnToggleEmoticons); emoticonsDropdown.attachButtonListener(this.btnToggleEmoticons, this.listenerSetter);
emoticonsDropdown.events.onOpen.push(this.onEmoticonsOpen); emoticonsDropdown.events.onOpen.push(this.onEmoticonsOpen);
emoticonsDropdown.events.onClose.push(this.onEmoticonsClose); emoticonsDropdown.events.onClose.push(this.onEmoticonsClose);

36
src/components/emoticonsDropdown/index.ts

@ -8,7 +8,7 @@ import { isTouchSupported } from "../../helpers/touchSupport";
import appChatsManager from "../../lib/appManagers/appChatsManager"; import appChatsManager from "../../lib/appManagers/appChatsManager";
import appImManager from "../../lib/appManagers/appImManager"; import appImManager from "../../lib/appManagers/appImManager";
import rootScope from "../../lib/rootScope"; import rootScope from "../../lib/rootScope";
import { blurActiveElement, whichChild } from "../../helpers/dom"; import { attachClickEvent, blurActiveElement, whichChild } from "../../helpers/dom";
import animationIntersector from "../animationIntersector"; import animationIntersector from "../animationIntersector";
import { horizontalMenu } from "../horizontalMenu"; import { horizontalMenu } from "../horizontalMenu";
import LazyLoadQueue, { LazyLoadQueueIntersector } from "../lazyLoadQueue"; import LazyLoadQueue, { LazyLoadQueueIntersector } from "../lazyLoadQueue";
@ -24,6 +24,7 @@ import AppGifsTab from "../sidebarRight/tabs/gifs";
import AppStickersTab from "../sidebarRight/tabs/stickers"; import AppStickersTab from "../sidebarRight/tabs/stickers";
import findUpClassName from "../../helpers/dom/findUpClassName"; import findUpClassName from "../../helpers/dom/findUpClassName";
import findUpTag from "../../helpers/dom/findUpTag"; import findUpTag from "../../helpers/dom/findUpTag";
import ListenerSetter from "../../helpers/listenerSetter";
export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown'; export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown';
@ -32,7 +33,9 @@ export interface EmoticonsTab {
onCloseAfterTimeout?: () => void onCloseAfterTimeout?: () => void
} }
const test = false; const KEEP_OPEN = false;
const TOGGLE_TIMEOUT = 200;
const ANIMATION_DURATION = 200;
export class EmoticonsDropdown { export class EmoticonsDropdown {
public static lazyLoadQueue = new LazyLoadQueue(); public static lazyLoadQueue = new LazyLoadQueue();
@ -72,36 +75,36 @@ export class EmoticonsDropdown {
this.element = document.getElementById('emoji-dropdown') as HTMLDivElement; this.element = document.getElementById('emoji-dropdown') as HTMLDivElement;
} }
public attachButtonListener(button: HTMLElement) { public attachButtonListener(button: HTMLElement, listenerSetter: ListenerSetter) {
let firstTime = true; let firstTime = true;
if(isTouchSupported) { if(isTouchSupported) {
button.addEventListener('click', () => { attachClickEvent(button, () => {
if(firstTime) { if(firstTime) {
firstTime = false; firstTime = false;
this.toggle(true); this.toggle(true);
} else { } else {
this.toggle(); this.toggle();
} }
}); }, {listenerSetter});
} else { } else {
button.onmouseover = (e) => { listenerSetter.add(button, 'mouseover', (e) => {
//console.log('onmouseover button'); //console.log('onmouseover button');
clearTimeout(this.displayTimeout);
//this.displayTimeout = setTimeout(() => {
if(firstTime) { if(firstTime) {
button.onmouseout = this.onMouseOut; listenerSetter.add(button, 'mouseout', this.onMouseOut);
firstTime = false; firstTime = false;
} }
clearTimeout(this.displayTimeout);
this.displayTimeout = window.setTimeout(() => {
this.toggle(true); this.toggle(true);
//}, 0/* 200 */); }, TOGGLE_TIMEOUT);
}; });
} }
} }
private onMouseOut = (e: MouseEvent) => { private onMouseOut = (e: MouseEvent) => {
if(test) return; if(KEEP_OPEN) return;
clearTimeout(this.displayTimeout);
if(!this.element.classList.contains('active')) return; if(!this.element.classList.contains('active')) return;
const toElement = (e as any).toElement as Element; const toElement = (e as any).toElement as Element;
@ -109,10 +112,9 @@ export class EmoticonsDropdown {
return; return;
} }
clearTimeout(this.displayTimeout);
this.displayTimeout = window.setTimeout(() => { this.displayTimeout = window.setTimeout(() => {
this.toggle(false); this.toggle(false);
}, 200); }, TOGGLE_TIMEOUT);
}; };
private init() { private init() {
@ -265,7 +267,7 @@ export class EmoticonsDropdown {
this.container.classList.remove('disable-hover'); this.container.classList.remove('disable-hover');
this.events.onOpenAfter.forEach(cb => cb()); this.events.onOpenAfter.forEach(cb => cb());
}, isTouchSupported ? 0 : 200); }, isTouchSupported ? 0 : ANIMATION_DURATION);
// ! can't use together with resizeObserver // ! can't use together with resizeObserver
/* if(isTouchSupported) { /* if(isTouchSupported) {
@ -302,7 +304,7 @@ export class EmoticonsDropdown {
this.container.classList.remove('disable-hover'); this.container.classList.remove('disable-hover');
this.events.onCloseAfter.forEach(cb => cb()); this.events.onCloseAfter.forEach(cb => cb());
}, isTouchSupported ? 0 : 200); }, isTouchSupported ? 0 : ANIMATION_DURATION);
/* if(isTouchSupported) { /* if(isTouchSupported) {
const scrollHeight = this.container.scrollHeight; const scrollHeight = this.container.scrollHeight;

13
src/components/emoticonsDropdown/tabs/stickers.ts

@ -209,11 +209,14 @@ export default class StickersTab implements EmoticonsTab {
//console.log('got stickerSet', stickerSet, li); //console.log('got stickerSet', stickerSet, li);
if(stickerSet.set.thumbs?.length) { if(stickerSet.set.thumbs?.length) {
EmoticonsDropdown.lazyLoadQueue.push({
div: button,
load: () => {
const downloadOptions = appStickersManager.getStickerSetThumbDownloadOptions(stickerSet.set); const downloadOptions = appStickersManager.getStickerSetThumbDownloadOptions(stickerSet.set);
const promise = appDownloadManager.download(downloadOptions); const promise = appDownloadManager.download(downloadOptions);
if(stickerSet.set.pFlags.animated) { if(stickerSet.set.pFlags.animated) {
promise return promise
.then(readBlobAsText) .then(readBlobAsText)
//.then(JSON.parse) //.then(JSON.parse)
.then(json => { .then(json => {
@ -229,17 +232,21 @@ export default class StickersTab implements EmoticonsTab {
}); });
} else { } else {
const image = new Image(); const image = new Image();
promise.then(blob => {
return promise.then(blob => {
renderImageFromUrl(image, URL.createObjectURL(blob), () => { renderImageFromUrl(image, URL.createObjectURL(blob), () => {
button.append(image); button.append(image);
}); });
}); });
} }
}
});
} else if(stickerSet.documents[0]._ !== 'documentEmpty') { // as thumb will be used first sticker } else if(stickerSet.documents[0]._ !== 'documentEmpty') { // as thumb will be used first sticker
wrapSticker({ wrapSticker({
doc: stickerSet.documents[0], doc: stickerSet.documents[0],
div: button as any, div: button as any,
group: EMOTICONSSTICKERGROUP group: EMOTICONSSTICKERGROUP,
lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue
}); // kostil }); // kostil
} }
} }

9
src/components/horizontalMenu.ts

@ -12,7 +12,7 @@ import { fastRaf } from "../helpers/schedulers";
import { FocusDirection } from "../helpers/fastSmoothScroll"; import { FocusDirection } from "../helpers/fastSmoothScroll";
import findUpAsChild from "../helpers/dom/findUpAsChild"; import findUpAsChild from "../helpers/dom/findUpAsChild";
export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 250, scrollableX?: ScrollableX) { export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?: (id: number, tabContent: HTMLDivElement, animate: boolean) => void | boolean, onTransitionEnd?: () => void, transitionTime = 250, scrollableX?: ScrollableX) {
const selectTab = TransitionSlider(content, tabs || content.dataset.animation === 'tabs' ? 'tabs' : 'navigation', transitionTime, onTransitionEnd); const selectTab = TransitionSlider(content, tabs || content.dataset.animation === 'tabs' ? 'tabs' : 'navigation', transitionTime, onTransitionEnd);
if(tabs) { if(tabs) {
@ -29,7 +29,12 @@ export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?
const selectTarget = (target: HTMLElement, id: number, animate = true) => { const selectTarget = (target: HTMLElement, id: number, animate = true) => {
const tabContent = content.children[id] as HTMLDivElement; const tabContent = content.children[id] as HTMLDivElement;
if(onClick) onClick(id, tabContent); if(onClick) {
const canChange = onClick(id, tabContent, animate);
if(canChange !== undefined && !canChange) {
return;
}
}
if(scrollableX) { if(scrollableX) {
scrollableX.scrollIntoViewNew(target.parentElement.children[id] as HTMLElement, 'center', undefined, undefined, animate ? undefined : FocusDirection.Static, transitionTime, 'x'); scrollableX.scrollIntoViewNew(target.parentElement.children[id] as HTMLElement, 'center', undefined, undefined, animate ? undefined : FocusDirection.Static, transitionTime, 'x');

6
src/components/inputField.ts

@ -159,6 +159,12 @@ class InputField {
} }
} }
if(label || placeholder) {
const border = document.createElement('div');
border.classList.add('input-field-border');
this.container.append(border);
}
if(label) { if(label) {
this.label = document.createElement('label'); this.label = document.createElement('label');
this.setLabel(); this.setLabel();

2
src/components/inputSearch.ts

@ -43,7 +43,7 @@ export default class InputSearch {
this.input.addEventListener('input', this.onInput); this.input.addEventListener('input', this.onInput);
this.clearBtn.addEventListener('click', this.onClearClick); this.clearBtn.addEventListener('click', this.onClearClick);
this.container.append(this.input, searchIcon, this.clearBtn); this.container.append(searchIcon, this.clearBtn);
} }
onInput = () => { onInput = () => {

4
src/components/scrollable.ts

@ -250,8 +250,8 @@ export class ScrollableX extends ScrollableBase {
if(!isTouchSupported) { if(!isTouchSupported) {
const scrollHorizontally = (e: any) => { const scrollHorizontally = (e: any) => {
if(!e.deltaX) { if(!e.deltaX && this.container.scrollWidth > this.container.clientWidth) {
this.container!.scrollLeft += e.deltaY / 4; this.container.scrollLeft += e.deltaY / 4;
cancelEvent(e); cancelEvent(e);
} }
}; };

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

@ -46,10 +46,10 @@ import Scrollable from "../../scrollable";
import { isTouchSupported } from "../../../helpers/touchSupport"; import { isTouchSupported } from "../../../helpers/touchSupport";
let setText = (text: string, row: Row) => { let setText = (text: string, row: Row) => {
fastRaf(() => { //fastRaf(() => {
row.title.innerHTML = text; row.title.innerHTML = text;
row.container.style.display = ''; row.container.style.display = '';
}); //});
}; };
type ListLoaderResult<T> = {count: number, items: any[]}; type ListLoaderResult<T> = {count: number, items: any[]};

4
src/config/app.ts

@ -12,8 +12,8 @@
const App = { const App = {
id: 1025907, id: 1025907,
hash: '452b0359b988148995f22ff0f4229750', hash: '452b0359b988148995f22ff0f4229750',
version: '0.4.1', version: '0.4.2',
langPackVersion: '0.1.3', langPackVersion: '0.1.4',
langPack: 'macos', langPack: 'macos',
langPackCode: 'en', langPackCode: 'en',
domains: [] as string[], domains: [] as string[],

9
src/lang.ts

@ -91,6 +91,15 @@ const lang = {
"Profile": "Profile", "Profile": "Profile",
"Saved": "Saved", "Saved": "Saved",
"ReportBug": "Report Bug", "ReportBug": "Report Bug",
"Notifications.Count": {
"one_value": "%d notification",
"other_value": "%d notifications",
},
"Notifications.Forwarded": {
"one_value": "Forwarded %d message",
"other_value": "Forwarded %d messages"
},
"Notifications.New": "New notification",
// * android // * android
"ActionCreateChannel": "Channel created", "ActionCreateChannel": "Channel created",

4
src/lib/appManagers/appChatsManager.ts

@ -477,7 +477,7 @@ export class AppChatsManager {
apiUpdatesManager.processUpdateMessage(updates); apiUpdatesManager.processUpdateMessage(updates);
const channelId = updates.chats[0].id; const channelId = updates.chats[0].id;
rootScope.broadcast('history_focus', -channelId); rootScope.broadcast('history_focus', {peerId: -channelId});
return channelId; return channelId;
}); });
@ -503,7 +503,7 @@ export class AppChatsManager {
apiUpdatesManager.processUpdateMessage(updates); apiUpdatesManager.processUpdateMessage(updates);
const chatId = (updates as any as Updates.updates).chats[0].id; const chatId = (updates as any as Updates.updates).chats[0].id;
rootScope.broadcast('history_focus', -chatId); rootScope.broadcast('history_focus', {peerId: -chatId});
return chatId; return chatId;
}); });

1
src/lib/appManagers/appDialogsManager.ts

@ -233,6 +233,7 @@ export class AppDialogsManager {
}; };
this.setListClickListener(archivedChatList, null, true); this.setListClickListener(archivedChatList, null, true);
//this.setListClickListener(archivedChatList, null, true); // * to test peer changing
this.chatsPreloader = putPreloader(null, true); this.chatsPreloader = putPreloader(null, true);

5
src/lib/appManagers/appImManager.ts

@ -169,8 +169,9 @@ export class AppImManager {
} }
}); });
rootScope.on('history_focus', (peerId) => { rootScope.on('history_focus', (e) => {
this.setInnerPeer(peerId); const {peerId, mid} = e;
this.setInnerPeer(peerId, mid);
}); });
rootScope.on('peer_changing', (chat) => { rootScope.on('peer_changing', (chat) => {

70
src/lib/appManagers/appMessagesManager.ts

@ -2953,7 +2953,11 @@ export class AppMessagesManager {
} }
} }
public canMessageBeEdited(message: any, kind: 'text' | 'poll') { private canMessageBeEdited(message: any, kind: 'text' | 'poll') {
if(message.pFlags.is_outgoing) {
return false;
}
const goodMedias = [ const goodMedias = [
'messageMediaPhoto', 'messageMediaPhoto',
'messageMediaDocument', 'messageMediaDocument',
@ -2992,7 +2996,7 @@ export class AppMessagesManager {
return true; return true;
} }
if((message.date < tsNow(true) - (2 * 86400) && message.media?._ !== 'messageMediaPoll') || !message.pFlags.out) { if((message.date < (tsNow(true) - (2 * 86400)) && message.media?._ !== 'messageMediaPoll') || !message.pFlags.out) {
return false; return false;
} }
@ -3005,7 +3009,7 @@ export class AppMessagesManager {
|| message.fromId === rootScope.myId || message.fromId === rootScope.myId
|| appChatsManager.getChat(message.peerId)._ === 'chat' || appChatsManager.getChat(message.peerId)._ === 'chat'
|| appChatsManager.hasRights(message.peerId, 'delete_messages') || appChatsManager.hasRights(message.peerId, 'delete_messages')
); ) && !message.pFlags.is_outgoing;
} }
public applyConversations(dialogsResult: MessagesPeerDialogs.messagesPeerDialogs) { public applyConversations(dialogsResult: MessagesPeerDialogs.messagesPeerDialogs) {
@ -4919,60 +4923,31 @@ export class AppMessagesManager {
peerTypeNotifySettings: PeerNotifySettings peerTypeNotifySettings: PeerNotifySettings
}> = {}) { }> = {}) {
const peerId = this.getMessagePeer(message); const peerId = this.getMessagePeer(message);
let peerString: string;
const notification: NotifyOptions = {}; const notification: NotifyOptions = {};
var notificationMessage: string, const peerString = appPeersManager.getPeerString(peerId);
notificationPhoto: any; let notificationMessage: string;
const _ = (str: string) => str;
const localSettings = appNotificationsManager.getLocalSettings();
if(options.peerTypeNotifySettings.show_previews) { if(options.peerTypeNotifySettings.show_previews) {
if(message._ === 'message' && message.fwd_from && options.fwdCount) { if(message._ === 'message' && message.fwd_from && options.fwdCount) {
notificationMessage = 'Forwarded ' + options.fwdCount + ' messages';//fwdMessagesPluralize(options.fwd_count); notificationMessage = I18n.format('Notifications.Forwarded', true, [options.fwdCount]);
} else { } else {
notificationMessage = this.wrapMessageForReply(message, undefined, undefined, true); notificationMessage = this.wrapMessageForReply(message, undefined, undefined, true);
} }
} else { } else {
notificationMessage = 'New notification'; notificationMessage = I18n.format('Notifications.New', true);
} }
if(peerId > 0) { notification.title = appPeersManager.getPeerTitle(peerId, true);
const fromUser = appUsersManager.getUser(message.fromId); if(peerId < 0 && message.fromId !== message.peerId) {
const fromPhoto = appUsersManager.getUserPhoto(message.fromId); notification.title = appPeersManager.getPeerTitle(message.fromId, true) +
notification.title = (fromUser.first_name || '') +
(fromUser.first_name && fromUser.last_name ? ' ' : '') +
(fromUser.last_name || '');
if(!notification.title) {
notification.title = fromUser.phone || _('conversation_unknown_user_raw');
}
notificationPhoto = fromPhoto;
peerString = appUsersManager.getUserString(peerId);
} else {
notification.title = appChatsManager.getChat(-peerId).title || _('conversation_unknown_chat_raw');
if(message.fromId) {
var fromUser = appUsersManager.getUser(message.fromId);
notification.title = (fromUser.first_name || fromUser.last_name || _('conversation_unknown_user_raw')) +
' @ ' + ' @ ' +
notification.title; notification.title;
} }
notificationPhoto = appChatsManager.getChatPhoto(-peerId);
peerString = appChatsManager.getChatString(-peerId);
}
notification.title = RichTextProcessor.wrapPlainText(notification.title); notification.title = RichTextProcessor.wrapPlainText(notification.title);
notification.onclick = () => { notification.onclick = () => {
/* rootScope.broadcast('history_focus', { rootScope.broadcast('history_focus', {peerId, mid: message.mid});
peerString: peerString,
messageID: message.flags & 16 ? message.mid : 0
}); */
}; };
notification.message = notificationMessage; notification.message = notificationMessage;
@ -4980,16 +4955,17 @@ export class AppMessagesManager {
notification.tag = peerString; notification.tag = peerString;
notification.silent = true;//message.pFlags.silent || false; notification.silent = true;//message.pFlags.silent || false;
/* if(notificationPhoto.location && !notificationPhoto.location.empty) { const peerPhoto = appPeersManager.getPeerPhoto(peerId);
apiManager.downloadSmallFile(notificationPhoto.location, notificationPhoto.size).then(function (blob) { if(peerPhoto) {
appProfileManager.loadAvatar(peerId, peerPhoto, 'photo_small').loadPromise.then(url => {
if(message.pFlags.unread) { if(message.pFlags.unread) {
notification.image = blob notification.image = url;
NotificationsManager.notify(notification) appNotificationsManager.notify(notification);
} }
}) });
} else { */ } else {
appNotificationsManager.notify(notification); appNotificationsManager.notify(notification);
//} }
} }
public getScheduledMessagesStorage(peerId: number) { public getScheduledMessagesStorage(peerId: number) {

10
src/lib/appManagers/appNotificationsManager.ts

@ -17,6 +17,7 @@ import { copy, deepEqual } from "../../helpers/object";
import { convertInputKeyToKey } from "../../helpers/string"; import { convertInputKeyToKey } from "../../helpers/string";
import { isMobile } from "../../helpers/userAgent"; import { isMobile } from "../../helpers/userAgent";
import { InputNotifyPeer, InputPeerNotifySettings, NotifyPeer, PeerNotifySettings, Update } from "../../layer"; import { InputNotifyPeer, InputPeerNotifySettings, NotifyPeer, PeerNotifySettings, Update } from "../../layer";
import I18n from "../langPack";
import apiManager from "../mtproto/mtprotoworker"; import apiManager from "../mtproto/mtprotoworker";
import rootScope from "../rootScope"; import rootScope from "../rootScope";
import sessionStorage from "../sessionStorage"; import sessionStorage from "../sessionStorage";
@ -55,10 +56,9 @@ export class AppNotificationsManager {
notifyChats: null as ImSadAboutIt, notifyChats: null as ImSadAboutIt,
notifyBroadcasts: null as ImSadAboutIt notifyBroadcasts: null as ImSadAboutIt
}; };
private exceptions: {[peerId: string]: PeerNotifySettings} = {}; //private exceptions: {[peerId: string]: PeerNotifySettings} = {};
private notifyContactsSignUp: Promise<boolean>; private notifyContactsSignUp: Promise<boolean>;
private faviconEl: HTMLLinkElement = document.head.querySelector('link[rel="icon"]'); private faviconEl: HTMLLinkElement = document.head.querySelector('link[rel="icon"]');
private langNotificationsPluralize = 'notifications';//_.pluralize('page_title_pluralize_notifications');
private titleBackup = document.title; private titleBackup = document.title;
private titleChanged = false; private titleChanged = false;
@ -221,7 +221,7 @@ export class AppNotificationsManager {
resetTitle(); resetTitle();
} else { } else {
this.titleChanged = true; this.titleChanged = true;
document.title = this.notificationsCount + ' ' + this.langNotificationsPluralize; document.title = I18n.format('Notifications.Count', true, [this.notificationsCount]);
//this.setFavicon('assets/img/favicon_unread.ico'); //this.setFavicon('assets/img/favicon_unread.ico');
// fetch('assets/img/favicon.ico') // fetch('assets/img/favicon.ico')
@ -499,8 +499,8 @@ export class AppNotificationsManager {
data.image = FileManager.getUrl(data.image, 'image/jpeg') data.image = FileManager.getUrl(data.image, 'image/jpeg')
} }
} }
else if (!data.image) */ { else */ if(!data.image) {
data.image = 'assets/img/logo.svg'; data.image = 'assets/img/logo_filled_rounded.png';
} }
// console.log('notify image', data.image) // console.log('notify image', data.image)

28
src/lib/appManagers/appProfileManager.ts

@ -37,7 +37,7 @@ export class AppProfileManager {
private savedAvatarURLs: { private savedAvatarURLs: {
[peerId: number]: { [peerId: number]: {
[size in PeerPhotoSize]?: string | Promise<any> [size in PeerPhotoSize]?: string | Promise<string>
} }
} = {}; } = {};
@ -455,11 +455,11 @@ export class AppProfileManager {
} }
} }
public putAvatar(div: HTMLElement, peerId: number, photo: UserProfilePhoto.userProfilePhoto | ChatPhoto.chatPhoto, size: PeerPhotoSize, img = new Image()) { public loadAvatar(peerId: number, photo: UserProfilePhoto.userProfilePhoto | ChatPhoto.chatPhoto, size: PeerPhotoSize) {
const inputPeer = appPeersManager.getInputPeerById(peerId); const inputPeer = appPeersManager.getInputPeerById(peerId);
let needFadeIn = true; let cached = false;
let getAvatarPromise: Promise<any>; let getAvatarPromise: Promise<string>;
let saved = this.savedAvatarURLs[peerId]; let saved = this.savedAvatarURLs[peerId];
if(!saved || !saved[size]) { if(!saved || !saved[size]) {
if(!saved) { if(!saved) {
@ -489,7 +489,7 @@ export class AppProfileManager {
const promise = appDownloadManager.download(downloadOptions); const promise = appDownloadManager.download(downloadOptions);
getAvatarPromise = saved[size] = promise.then(blob => { getAvatarPromise = saved[size] = promise.then(blob => {
saved[size] = URL.createObjectURL(blob); return saved[size] = URL.createObjectURL(blob);
/* if(str) { /* if(str) {
console.log(str, Date.now() / 1000, Date.now() - time); console.log(str, Date.now() / 1000, Date.now() - time);
@ -498,12 +498,18 @@ export class AppProfileManager {
} else if(typeof(saved[size]) !== 'string') { } else if(typeof(saved[size]) !== 'string') {
getAvatarPromise = saved[size] as Promise<any>; getAvatarPromise = saved[size] as Promise<any>;
} else { } else {
getAvatarPromise = Promise.resolve(); getAvatarPromise = Promise.resolve(saved[size]);
needFadeIn = false; cached = true;
}
return {cached, loadPromise: getAvatarPromise};
} }
public putAvatar(div: HTMLElement, peerId: number, photo: UserProfilePhoto.userProfilePhoto | ChatPhoto.chatPhoto, size: PeerPhotoSize, img = new Image()) {
const {cached, loadPromise} = this.loadAvatar(peerId, photo, size);
let callback: () => void; let callback: () => void;
if(!needFadeIn) { if(cached) {
// смотри в misc.ts: renderImageFromUrl // смотри в misc.ts: renderImageFromUrl
callback = () => { callback = () => {
replaceContent(div, img); replaceContent(div, img);
@ -532,16 +538,16 @@ export class AppProfileManager {
}; };
} }
const loadPromise = getAvatarPromise.then(() => { const renderPromise = loadPromise.then((url) => {
return new Promise<void>((resolve) => { return new Promise<void>((resolve) => {
renderImageFromUrl(img, saved[size] as string, () => { renderImageFromUrl(img, url, () => {
callback(); callback();
resolve(); resolve();
}/* , false */); }/* , false */);
}); });
}); });
return {cached: !needFadeIn, loadPromise}; return {cached, loadPromise: renderPromise};
} }
// peerId === peerId || title // peerId === peerId || title

19
src/lib/mtproto/networker.ts

@ -74,6 +74,7 @@ export type MTMessage = InvokeApiOptions & MTMessageOptions & {
}; };
const CONNECTION_TIMEOUT = 5000; const CONNECTION_TIMEOUT = 5000;
let invokeAfterMsgConstructor: number;
export default class MTPNetworker { export default class MTPNetworker {
private authKeyUint8: Uint8Array; private authKeyUint8: Uint8Array;
@ -320,15 +321,21 @@ export default class MTPNetworker {
} }
if(options.afterMessageId) { if(options.afterMessageId) {
const invokeAfterMsg = Schema.API.methods.find(m => m.method === 'invokeAfterMsg'); if(invokeAfterMsgConstructor === undefined) {
if(!invokeAfterMsg) throw new Error('no invokeAfterMsg!'); const m = Schema.API.methods.find(m => m.method === 'invokeAfterMsg');
invokeAfterMsgConstructor = m ? +m.id >>> 0 : 0;
if(this.debug) {
this.log('Api call options.afterMessageId!');
} }
serializer.storeInt(+invokeAfterMsg.id >>> 0, 'invokeAfterMsg'); if(invokeAfterMsgConstructor) {
//if(this.debug) {
//this.log('Api call options.afterMessageId!');
//}
serializer.storeInt(invokeAfterMsgConstructor, 'invokeAfterMsg');
serializer.storeLong(options.afterMessageId, 'msg_id'); serializer.storeLong(options.afterMessageId, 'msg_id');
} else {
this.log.error('no invokeAfterMsg!');
}
} }
options.resultType = serializer.storeMethod(method, params); options.resultType = serializer.storeMethod(method, params);

5
src/lib/mtproto/serverTimeManager.ts

@ -9,6 +9,7 @@
* https://github.com/zhukov/webogram/blob/master/LICENSE * https://github.com/zhukov/webogram/blob/master/LICENSE
*/ */
import { MOUNT_CLASS_TO } from '../../config/debug';
import { tsNow } from '../../helpers/date'; import { tsNow } from '../../helpers/date';
import sessionStorage from '../sessionStorage'; import sessionStorage from '../sessionStorage';
@ -37,4 +38,6 @@ export class ServerTimeManager {
} }
} }
export default new ServerTimeManager(); const serverTimeManager = new ServerTimeManager();
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.serverTimeManager = serverTimeManager);
export default serverTimeManager;

2
src/lib/rootScope.ts

@ -51,7 +51,7 @@ export type BroadcastEvents = {
'history_delete': {peerId: number, msgs: {[mid: number]: true}}, 'history_delete': {peerId: number, msgs: {[mid: number]: true}},
'history_forbidden': number, 'history_forbidden': number,
'history_reload': number, 'history_reload': number,
'history_focus': number, 'history_focus': {peerId: number, mid?: number},
//'history_request': void, //'history_request': void,
'message_edit': {storage: MessagesStorage, peerId: number, mid: number}, 'message_edit': {storage: MessagesStorage, peerId: number, mid: number},

3
src/pages/pageAuthCode.ts

@ -17,6 +17,7 @@ import TrackingMonkey from '../components/monkeys/tracking';
import CodeInputField from '../components/codeInputField'; import CodeInputField from '../components/codeInputField';
import { replaceContent } from '../helpers/dom'; import { replaceContent } from '../helpers/dom';
import { i18n, LangPackKey } from '../lib/langPack'; import { i18n, LangPackKey } from '../lib/langPack';
import { randomLong } from '../helpers/random';
let authCode: AuthSentCode.authSentCode = null; let authCode: AuthSentCode.authSentCode = null;
@ -29,7 +30,7 @@ let onFirstMount = (): Promise<any> => {
const codeInputField = new CodeInputField({ const codeInputField = new CodeInputField({
label: 'Code', label: 'Code',
name: 'code', name: randomLong(),
length: CODELENGTH, length: CODELENGTH,
onFill: (code) => { onFill: (code) => {
submitCode('' + code); submitCode('' + code);

63
src/pages/pageSignIn.ts

@ -25,6 +25,8 @@ import { LangPackString } from "../layer";
import lottieLoader from "../lib/lottieLoader"; import lottieLoader from "../lib/lottieLoader";
import { ripple } from "../components/ripple"; import { ripple } from "../components/ripple";
import findUpTag from "../helpers/dom/findUpTag"; import findUpTag from "../helpers/dom/findUpTag";
import findUpClassName from "../helpers/dom/findUpClassName";
import { randomLong } from "../helpers/random";
type Country = _Country & { type Country = _Country & {
li?: HTMLLIElement[] li?: HTMLLIElement[]
@ -56,14 +58,14 @@ let onFirstMount = () => {
const countryInputField = new InputField({ const countryInputField = new InputField({
label: 'Login.CountrySelectorLabel', label: 'Login.CountrySelectorLabel',
name: 'countryCode', name: randomLong(),
plainText: true plainText: true
}); });
countryInputField.container.classList.add('input-select'); countryInputField.container.classList.add('input-select');
const countryInput = countryInputField.input as HTMLInputElement; const countryInput = countryInputField.input as HTMLInputElement;
countryInput.autocomplete = 'rrRandomRR'; countryInput.autocomplete = randomLong();
const selectWrapper = document.createElement('div'); const selectWrapper = document.createElement('div');
selectWrapper.classList.add('select-wrapper', 'z-depth-3', 'hide'); selectWrapper.classList.add('select-wrapper', 'z-depth-3', 'hide');
@ -77,38 +79,25 @@ let onFirstMount = () => {
const scroll = new Scrollable(selectWrapper); const scroll = new Scrollable(selectWrapper);
let initedSelect = false;
let initSelect = () => { let initSelect = () => {
initSelect = null; initSelect = null;
countries.forEach((c) => { countries.forEach((c) => {
initedSelect = true; const emoji = c.emoji;
/* let unified = unifiedCountryCodeEmoji(c.code);
let emoji = unified.split('-').reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), ''); */
//let emoji = countryCodeEmoji(c.code);
let emoji = c.emoji;
let liArr: Array<HTMLLIElement> = []; const liArr: Array<HTMLLIElement> = [];
c.phoneCode.split(' and ').forEach((phoneCode: string) => { c.phoneCode.split(' and ').forEach((phoneCode: string) => {
let li = document.createElement('li'); const li = document.createElement('li');
var spanEmoji = document.createElement('span'); const spanEmoji = document.createElement('span');
/* spanEmoji.innerHTML = countryCodeEmoji(c.code); */
//spanEmoji.classList.add('emoji-outer', 'emoji-sizer');
//spanEmoji.innerHTML = `<span class="emoji-inner" style="background: url(${sheetUrl}${sheetNo}.png);background-position:${xPos}% ${yPos}%;background-size:${sizeX}% ${sizeY}%" data-codepoints="${unified}"></span>`;
const kek = RichTextProcessor.wrapRichText(emoji);
let kek = RichTextProcessor.wrapRichText(emoji);
//console.log(c.name, emoji, kek, spanEmoji.innerHTML);
li.appendChild(spanEmoji); li.appendChild(spanEmoji);
spanEmoji.outerHTML = kek; spanEmoji.outerHTML = kek;
li.append(c.name); li.append(c.name);
var span = document.createElement('span'); const span = document.createElement('span');
span.classList.add('phone-code'); span.classList.add('phone-code');
span.innerText = '+' + phoneCode; span.innerText = '+' + phoneCode;
li.appendChild(span); li.appendChild(span);
@ -120,22 +109,31 @@ let onFirstMount = () => {
c.li = liArr; c.li = liArr;
}); });
selectList.addEventListener('mousedown', function(e) { selectList.addEventListener('mousedown', (e) => {
if(e.button !== 0) { // other buttons but left shall not pass
return;
}
let target = e.target as HTMLElement; let target = e.target as HTMLElement;
if(target.tagName !== 'LI') target = findUpTag(target, 'LI'); if(target.tagName !== 'LI') target = findUpTag(target, 'LI');
let countryName = target.childNodes[1].textContent;//target.innerText.split('\n').shift(); selectCountryByTarget(target);
let phoneCode = target.querySelector<HTMLElement>('.phone-code').innerText; //console.log('clicked', e, countryName, phoneCode);
});
countryInputField.container.appendChild(selectWrapper);
};
const selectCountryByTarget = (target: HTMLElement) => {
const countryName = target.childNodes[1].textContent;//target.innerText.split('\n').shift();
const phoneCode = target.querySelector<HTMLElement>('.phone-code').innerText;
countryInput.value = countryName; countryInput.value = countryName;
lastCountrySelected = countries.find(c => c.name === countryName); lastCountrySelected = countries.find(c => c.name === countryName);
telEl.value = lastValue = phoneCode; telEl.value = lastValue = phoneCode;
hidePicker();
setTimeout(() => telEl.focus(), 0); setTimeout(() => telEl.focus(), 0);
//console.log('clicked', e, countryName, phoneCode);
});
countryInputField.container.appendChild(selectWrapper);
}; };
initSelect(); initSelect();
@ -152,6 +150,7 @@ let onFirstMount = () => {
} }
clearTimeout(hideTimeout); clearTimeout(hideTimeout);
hideTimeout = undefined;
selectWrapper.classList.remove('hide'); selectWrapper.classList.remove('hide');
void selectWrapper.offsetWidth; // reflow void selectWrapper.offsetWidth; // reflow
@ -171,9 +170,9 @@ let onFirstMount = () => {
let mouseDownHandlerAttached = false; let mouseDownHandlerAttached = false;
const onMouseDown = (e: MouseEvent) => { const onMouseDown = (e: MouseEvent) => {
/* if(findUpClassName(e.target, 'input-select')) { if(findUpClassName(e.target, 'input-select')) {
return; return;
} */ }
if(e.target === countryInput) { if(e.target === countryInput) {
return; return;
} }
@ -184,9 +183,11 @@ let onFirstMount = () => {
}; };
const hidePicker = () => { const hidePicker = () => {
if(hideTimeout !== undefined) return;
selectWrapper.classList.remove('active'); selectWrapper.classList.remove('active');
hideTimeout = window.setTimeout(() => { hideTimeout = window.setTimeout(() => {
selectWrapper.classList.add('hide'); selectWrapper.classList.add('hide');
hideTimeout = undefined;
}, 200); }, 200);
}; };
/* false && countryInput.addEventListener('blur', function(this: typeof countryInput, e) { /* false && countryInput.addEventListener('blur', function(this: typeof countryInput, e) {
@ -221,6 +222,8 @@ let onFirstMount = () => {
countries.forEach((c) => { countries.forEach((c) => {
c.li.forEach(li => li.style.display = ''); c.li.forEach(li => li.style.display = '');
}); });
} else if(matches.length === 1 && e.key === 'Enter') {
selectCountryByTarget(matches[0].li[0]);
} }
}); });

4
src/scss/partials/_audio.scss

@ -359,6 +359,10 @@
//overflow: visible!important; //overflow: visible!important;
opacity: .3; opacity: .3;
@include hover() {
opacity: 1;
}
&.active, .audio.is-unread:not(.is-out) & { &.active, .audio.is-unread:not(.is-out) & {
opacity: 1; opacity: 1;
} }

4
src/scss/partials/_button.scss

@ -112,7 +112,7 @@
&.active { &.active {
visibility: visible; visibility: visible;
opacity: 1; opacity: 1;
transform: scale(1); transform: scale3d(1, 1, 1); // * scale3d (NOT scale) will fix jumping text
} }
&:not(.active) { &:not(.active) {
@ -178,7 +178,7 @@
@include hover-background-effect(); @include hover-background-effect();
&.danger { &.danger {
@include hover-background-effect(red); @include hover-background-effect(danger);
} }
&:before { &:before {

12
src/scss/partials/_chat.scss

@ -1042,12 +1042,6 @@ $chat-helper-size: 39px;
//} //}
} }
.preloader-container {
.preloader-circular {
background-color: rgba(0, 0, 0, .3);
}
}
.search-group.search-group-messages { .search-group.search-group-messages {
padding: 0.25rem 0 .5rem; padding: 0.25rem 0 .5rem;
} }
@ -1139,7 +1133,7 @@ $chat-helper-size: 39px;
position: absolute; position: absolute;
background-color: var(--surface-color); background-color: var(--surface-color);
border-radius: 50%; border-radius: 50%;
color: $placeholder-color; color: var(--secondary-text-color);
font-size: 1.5rem; font-size: 1.5rem;
display: flex; display: flex;
align-items: center; align-items: center;
@ -1172,6 +1166,10 @@ $chat-helper-size: 39px;
top: -.25rem; top: -.25rem;
right: -.25rem; right: -.25rem;
&.badge-primary {
background-color: var(--chatlist-status-color);
}
@include respond-to(handhelds) { @include respond-to(handhelds) {
top: -.75rem; top: -.75rem;
right: .1875rem; right: .1875rem;

4
src/scss/partials/_chatBubble.scss

@ -1279,8 +1279,8 @@ $bubble-margin: .25rem;
visibility: visible; visibility: visible;
} }
.tgico-pinnedchat:before { .tgico-pinnedchat {
font-weight: bold; margin-right: .125rem;
} }
} }

50
src/scss/partials/_input.scss

@ -11,6 +11,7 @@
.input-field { .input-field {
--height: 54px; --height: 54px;
--border-radius: #{$border-radius-medium};
position: relative; position: relative;
@include respond-to(handhelds) { @include respond-to(handhelds) {
@ -63,26 +64,38 @@
} }
} }
&-border {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border: 2px solid var(--primary-color);
opacity: 0;
border-radius: var(--border-radius);
pointer-events: none;
@include animation-level(2) {
transition: opacity .2s;
}
}
&-input { &-input {
--padding: 1.0625rem; --padding: 1rem;
--padding-horizontal: 1rem; --padding-horizontal: 1rem;
--border-width: 1px; --border-width: 1px;
--border-width-top: 2px;
border: var(--border-width) solid var(--input-search-border-color); border: var(--border-width) solid var(--input-search-border-color);
border-radius: $border-radius-medium; border-radius: var(--border-radius);
background-color: transparent; background-color: transparent;
//padding: 0 1rem; //padding: 0 1rem;
padding: calc(var(--padding) - var(--border-width-top)) calc(var(--padding-horizontal) - var(--border-width)); padding: calc(var(--padding) - var(--border-width));
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
min-height: var(--height); min-height: var(--height);
transition: 0s border-color; transition: 0s border-color;
position: relative; position: relative;
z-index: 1; z-index: 1;
line-height: 1.3125; line-height: var(--line-height);
//line-height: calc(54px - var(--border-width));
/* overflow: hidden;
white-space: nowrap; */
@include respond-to(handhelds) { @include respond-to(handhelds) {
--padding: .9375rem; --padding: .9375rem;
@ -116,8 +129,6 @@
} */ } */
&:focus { &:focus {
--border-width: 2px;
--border-width-top: 3px;
border-color: var(--primary-color); border-color: var(--primary-color);
//padding: 0 calc(1rem - 1px); //padding: 0 calc(1rem - 1px);
} }
@ -133,6 +144,10 @@
& ~ label { & ~ label {
color: var(--danger-color) !important; color: var(--danger-color) !important;
} }
& ~ .input-field-border {
border-color: var(--danger-color) !important;
}
} }
&.valid { &.valid {
@ -141,6 +156,10 @@
& ~ label { & ~ label {
color: #26962F !important; color: #26962F !important;
} }
& ~ .input-field-border {
border-color: #26962F !important;
}
} }
/* &.error, &.valid { /* &.error, &.valid {
@ -158,11 +177,15 @@
font-weight: 500; font-weight: 500;
} }
// * valid for plain text, empty for contenteditable &:focus ~ .input-field-border {
opacity: 1;
}
/* // * valid for plain text, empty for contenteditable
&:valid ~ label, &:valid ~ label,
&:not(:empty):focus ~ label { &:not(:empty):focus ~ label {
transition-delay: 0s, 0s, 0s, 0s; transition-delay: 0s, 0s, 0s, 0s;
} } */
&:focus ~ label, &:focus ~ label,
&:valid ~ label, &:valid ~ label,
@ -252,6 +275,7 @@ input:focus, button:focus {
margin-left: .4375rem; margin-left: .4375rem;
margin-right: .4375rem; margin-right: .4375rem;
overflow: hidden; overflow: hidden;
--border-radius: 22px;
@include respond-to(handhelds) { @include respond-to(handhelds) {
margin-left: 1rem; margin-left: 1rem;
@ -265,7 +289,6 @@ input:focus, button:focus {
min-height: var(--height) !important; min-height: var(--height) !important;
max-height: var(--height) !important; max-height: var(--height) !important;
//line-height: calc(var(--height) + 2px - var(--border-width) * 2); //line-height: calc(var(--height) + 2px - var(--border-width) * 2);
border-radius: 22px;
border-color: var(--input-search-border-color); border-color: var(--input-search-border-color);
line-height: var(--height); line-height: var(--height);
@ -281,7 +304,6 @@ input:focus, button:focus {
} }
&:focus { &:focus {
--border-width: 2px;
background-color: transparent; background-color: transparent;
border-color: var(--primary-color); border-color: var(--primary-color);

2
src/scss/partials/_preloader.scss

@ -70,7 +70,7 @@ $transition: .2s ease-in-out;
.preloader-circular { .preloader-circular {
animation: none; animation: none;
background-color: rgba(0, 0, 0, .7); background-color: rgba(0, 0, 0, .3);
border-radius: 50%; border-radius: 50%;
width: 100%; width: 100%;
height: 100%; height: 100%;

5
src/scss/partials/_rightSidebar.scss

@ -173,6 +173,7 @@
justify-content: center; justify-content: center;
font-size: 1rem; font-size: 1rem;
line-height: var(--line-height); line-height: var(--line-height);
border-radius: 0;
} }
.menu-horizontal-div i { .menu-horizontal-div i {
@ -287,6 +288,10 @@
> div:first-child { > div:first-child {
transform: translateY(0); transform: translateY(0);
// * fix saving scroll on tab switching, when FROM tab has height < 100vh, and another is scrolled less than the FROM tab's height
// * adding 1 extra pixel for scroll
min-height: calc(100vh - 111px);
} }
} }

11
src/scss/partials/popups/_createPoll.scss

@ -45,11 +45,16 @@
.btn-icon { .btn-icon {
position: absolute; position: absolute;
right: .5rem; right: .4375rem;
top: .5rem; top: .4375rem;
z-index: 1; z-index: 1;
opacity: 1; opacity: 1;
transition: opacity .2s ease; transition: opacity .2s ease;
@include respond-to(handhelds) {
right: .3125rem;
top: .3125rem;
}
} }
/* &:last-child:not(:nth-child(10)) { /* &:last-child:not(:nth-child(10)) {
.btn-icon { .btn-icon {
@ -65,7 +70,7 @@
} }
.poll-create-questions { .poll-create-questions {
padding: 0px 1.25rem 2.03125rem; padding: 0px 1.25rem 1.5rem;
.input-field-input { .input-field-input {
padding-right: 3.25rem; padding-right: 3.25rem;

Loading…
Cancel
Save