Browse Source

Profile improvements

Hide scrollbar on auth pages
master
morethanwords 3 years ago
parent
commit
bfc1db799c
  1. 50
      src/components/appMediaViewer.ts
  2. 4
      src/components/appSearchSuper..ts
  3. 115
      src/components/avatar.ts
  4. 35
      src/components/chat/bubbles.ts
  5. 2
      src/components/chat/chat.ts
  6. 9
      src/components/chat/dragAndDrop.ts
  7. 6
      src/components/chat/input.ts
  8. 2
      src/components/chat/selection.ts
  9. 9
      src/components/chat/stickersHelper.ts
  10. 6
      src/components/chat/topbar.ts
  11. 64
      src/components/emoticonsDropdown/tabs/emoji.ts
  12. 5
      src/components/popups/newMedia.ts
  13. 24
      src/components/ripple.ts
  14. 24
      src/components/row.ts
  15. 4
      src/components/scrollable.ts
  16. 3
      src/components/sidebarLeft/tabs/contacts.ts
  17. 77
      src/components/sidebarLeft/tabs/language.ts
  18. 159
      src/components/sidebarRight/tabs/sharedMedia.ts
  19. 8
      src/components/sortedUserList.ts
  20. 4
      src/components/swipeHandler.ts
  21. 4
      src/components/wrappers.ts
  22. 2
      src/config/app.ts
  23. 50
      src/helpers/dom/getVisibleRect.ts
  24. 20
      src/helpers/mediaSizes.ts
  25. 15
      src/index.ts
  26. 16
      src/lang.ts
  27. 48
      src/lib/appManagers/appImManager.ts
  28. 20
      src/lib/appManagers/appMessagesManager.ts
  29. 11
      src/lib/appManagers/appPhotosManager.ts
  30. 8
      src/lib/sessionStorage.ts
  31. 3
      src/pages/pageSignIn.ts
  32. 11
      src/pages/pagesManager.ts
  33. 9
      src/scss/partials/_avatar.scss
  34. 43
      src/scss/partials/_button.scss
  35. 6
      src/scss/partials/_chat.scss
  36. 11
      src/scss/partials/_chatBubble.scss
  37. 5
      src/scss/partials/_chatTopbar.scss
  38. 4
      src/scss/partials/_chatlist.scss
  39. 44
      src/scss/partials/_checkbox.scss
  40. 4
      src/scss/partials/_emojiDropdown.scss
  41. 35
      src/scss/partials/_leftSidebar.scss
  42. 8
      src/scss/partials/_mediaViewer.scss
  43. 36
      src/scss/partials/_profile.scss
  44. 44
      src/scss/partials/_rightSidebar.scss
  45. 110
      src/scss/partials/_row.scss
  46. 23
      src/scss/partials/_slider.scss
  47. 5
      src/scss/partials/pages/_pages.scss
  48. 7
      src/scss/partials/popups/_mediaAttacher.scss
  49. 115
      src/scss/style.scss

50
src/components/appMediaViewer.ts

@ -39,6 +39,8 @@ import { forEachReverse } from "../helpers/array";
import AppSharedMediaTab from "./sidebarRight/tabs/sharedMedia"; import AppSharedMediaTab from "./sidebarRight/tabs/sharedMedia";
import findUpClassName from "../helpers/dom/findUpClassName"; import findUpClassName from "../helpers/dom/findUpClassName";
import renderImageFromUrl from "../helpers/dom/renderImageFromUrl"; import renderImageFromUrl from "../helpers/dom/renderImageFromUrl";
import findUpAsChild from "../helpers/dom/findUpAsChild";
import getVisibleRect from "../helpers/dom/getVisibleRect";
// TODO: масштабирование картинок (не SVG) при ресайзе, и правильный возврат на исходную позицию // TODO: масштабирование картинок (не SVG) при ресайзе, и правильный возврат на исходную позицию
// TODO: картинки "обрезаются" если возвращаются или появляются с места, где есть их перекрытие (топбар, поле ввода) // TODO: картинки "обрезаются" если возвращаются или появляются с места, где есть их перекрытие (топбар, поле ввода)
@ -332,10 +334,6 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
protected async setMoverToTarget(target: HTMLElement, closing = false, fromRight = 0) { protected async setMoverToTarget(target: HTMLElement, closing = false, fromRight = 0) {
const mover = this.content.mover; const mover = this.content.mover;
if(!target) {
target = this.content.media;
}
if(!closing) { if(!closing) {
mover.innerHTML = ''; mover.innerHTML = '';
//mover.append(this.buttons.prev, this.buttons.next); //mover.append(this.buttons.prev, this.buttons.next);
@ -361,16 +359,44 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
let rect: DOMRect; let rect: DOMRect;
if(target) { if(target) {
if(target instanceof AvatarElement) { if(target instanceof AvatarElement || target.classList.contains('grid-item')) {
realParent = target; realParent = target;
rect = target.getBoundingClientRect(); rect = target.getBoundingClientRect();
} else if(target instanceof SVGImageElement || target.parentElement instanceof SVGForeignObjectElement) { } else if(target instanceof SVGImageElement || target.parentElement instanceof SVGForeignObjectElement) {
realParent = findUpClassName(target, 'attachment'); realParent = findUpClassName(target, 'attachment');
rect = realParent.getBoundingClientRect(); rect = realParent.getBoundingClientRect();
} else { } else if(target.classList.contains('profile-avatars-avatar')) {
realParent = findUpClassName(target, 'profile-avatars-container');
rect = realParent.getBoundingClientRect();
// * if not active avatar
if(closing && target.getBoundingClientRect().left !== rect.left) {
target = realParent = rect = undefined;
}
}
}
if(!target) {
target = this.content.media;
}
if(!rect) {
realParent = target.parentElement as HTMLElement; realParent = target.parentElement as HTMLElement;
rect = target.getBoundingClientRect(); rect = target.getBoundingClientRect();
} }
let needOpacity = false;
if(target !== this.content.media && !target.classList.contains('profile-avatars-avatar')) {
const overflowElement = findUpClassName(realParent, 'scrollable');
const visibleRect = getVisibleRect(realParent, overflowElement);
if(closing && (!visibleRect || visibleRect.overflow.vertical === 2 || visibleRect.overflow.horizontal === 2)) {
target = this.content.media;
realParent = target.parentElement as HTMLElement;
rect = target.getBoundingClientRect();
} else if(visibleRect && (visibleRect.overflow.vertical === 1 || visibleRect.overflow.horizontal === 1)) {
needOpacity = true;
}
} }
const containerRect = this.content.media.getBoundingClientRect(); const containerRect = this.content.media.getBoundingClientRect();
@ -444,6 +470,8 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
mover.style.transform = transform; mover.style.transform = transform;
needOpacity && (mover.style.opacity = '0'/* !closing ? '0' : '' */);
/* if(wasActive) { /* if(wasActive) {
this.log('setMoverToTarget', mover.style.transform); this.log('setMoverToTarget', mover.style.transform);
} */ } */
@ -612,6 +640,8 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
return ret; return ret;
} }
mover.classList.toggle('opening', !closing);
//await new Promise((resolve) => setTimeout(resolve, 0)); //await new Promise((resolve) => setTimeout(resolve, 0));
//await new Promise((resolve) => window.requestAnimationFrame(resolve)); //await new Promise((resolve) => window.requestAnimationFrame(resolve));
// * одного RAF'а недостаточно, иногда анимация с одним не срабатывает (преимущественно на мобильных) // * одного RAF'а недостаточно, иногда анимация с одним не срабатывает (преимущественно на мобильных)
@ -624,6 +654,7 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
mover.style.transform = `translate3d(${containerRect.left}px,${containerRect.top}px,0) scale3d(1,1,1)`; mover.style.transform = `translate3d(${containerRect.left}px,${containerRect.top}px,0) scale3d(1,1,1)`;
//mover.style.transform = `translate(-50%,-50%) scale(1,1)`; //mover.style.transform = `translate(-50%,-50%) scale(1,1)`;
needOpacity && (mover.style.opacity = ''/* closing ? '0' : '' */);
if(aspecter) { if(aspecter) {
this.setFullAspect(aspecter, containerRect, rect); this.setFullAspect(aspecter, containerRect, rect);
@ -1109,7 +1140,8 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
onAnimationEnd.then(() => { onAnimationEnd.then(() => {
if(!media.url) { if(!media.url) {
this.preloader.attach(mover, true, cancellablePromise); this.preloader.attachPromise(cancellablePromise);
//this.preloader.attach(mover, true, cancellablePromise);
} }
}); });
@ -1157,9 +1189,11 @@ class AppMediaViewerBase<ContentAdditionType extends string, ButtonsAdditionType
} }
} }
this.preloader.detach(); //this.preloader.detach();
}).catch(err => { }).catch(err => {
this.log.error(err); this.log.error(err);
this.preloader.attach(mover);
this.preloader.setManual();
}); });
return cancellablePromise; return cancellablePromise;

4
src/components/appSearchSuper..ts

@ -5,7 +5,7 @@
*/ */
import { formatDateAccordingToToday, months } from "../helpers/date"; import { formatDateAccordingToToday, months } from "../helpers/date";
import { positionElementByIndex, isInDOM, replaceContent } from "../helpers/dom"; import { positionElementByIndex } from "../helpers/dom";
import { copy, getObjectKeysAndSort, safeAssign } from "../helpers/object"; import { copy, getObjectKeysAndSort, safeAssign } from "../helpers/object";
import { escapeRegExp, limitSymbols } from "../helpers/string"; import { escapeRegExp, limitSymbols } from "../helpers/string";
import appChatsManager from "../lib/appManagers/appChatsManager"; import appChatsManager from "../lib/appManagers/appChatsManager";
@ -124,7 +124,7 @@ export default class AppSearchSuper {
this.container.classList.add('search-super'); this.container.classList.add('search-super');
const navScrollableContainer = document.createElement('div'); const navScrollableContainer = document.createElement('div');
navScrollableContainer.classList.add('search-super-tabs-scrollable', 'menu-horizontal-scrollable'); navScrollableContainer.classList.add('search-super-tabs-scrollable', 'menu-horizontal-scrollable', 'sticky');
const navScrollable = new ScrollableX(navScrollableContainer); const navScrollable = new ScrollableX(navScrollableContainer);

115
src/components/avatar.ts

@ -9,7 +9,7 @@ import appProfileManager from "../lib/appManagers/appProfileManager";
import rootScope from "../lib/rootScope"; import rootScope from "../lib/rootScope";
import { attachClickEvent, cancelEvent } from "../helpers/dom"; import { attachClickEvent, cancelEvent } from "../helpers/dom";
import AppMediaViewer, { AppMediaViewerAvatar } from "./appMediaViewer"; import AppMediaViewer, { AppMediaViewerAvatar } from "./appMediaViewer";
import { Photo } from "../layer"; import { Message, Photo } from "../layer";
import appPeersManager from "../lib/appManagers/appPeersManager"; import appPeersManager from "../lib/appManagers/appPeersManager";
//import type { LazyLoadQueueIntersector } from "./lazyLoadQueue"; //import type { LazyLoadQueueIntersector } from "./lazyLoadQueue";
@ -24,91 +24,98 @@ const onAvatarUpdate = (peerId: number) => {
rootScope.on('avatar_update', onAvatarUpdate); rootScope.on('avatar_update', onAvatarUpdate);
rootScope.on('peer_title_edit', onAvatarUpdate); rootScope.on('peer_title_edit', onAvatarUpdate);
export default class AvatarElement extends HTMLElement { export async function openAvatarViewer(target: HTMLElement, peerId: number, middleware: () => boolean, message?: any, prevTargets?: {element: HTMLElement, item: string | Message.messageService}[], nextTargets?: typeof prevTargets) {
private peerId: number; const photo = await appProfileManager.getFullPhoto(peerId);
private isDialog = false; if(!middleware() || !photo) {
public peerTitle: string;
public loadPromises: Promise<any>[];
//public lazyLoadQueue: LazyLoadQueueIntersector;
//private addedToQueue = false;
constructor() {
super();
// элемент создан
}
connectedCallback() {
// браузер вызывает этот метод при добавлении элемента в документ
// (может вызываться много раз, если элемент многократно добавляется/удаляется)
this.isDialog = !!this.getAttribute('dialog');
if(this.getAttribute('clickable') === '') {
this.setAttribute('clickable', 'set');
let loading = false;
attachClickEvent(this, async(e) => {
cancelEvent(e);
if(loading) return;
//console.log('avatar clicked');
const peerId = this.peerId;
loading = true;
const photo = await appProfileManager.getFullPhoto(this.peerId);
if(this.peerId !== peerId || !photo) {
loading = false;
return; return;
} }
const getTarget = () => {
const good = Array.from(target.querySelectorAll('img')).find(img => !img.classList.contains('emoji'));
return good ? target : null;
};
if(peerId < 0) { if(peerId < 0) {
const maxId = Number.MAX_SAFE_INTEGER; const hadMessage = !!message;
const inputFilter = 'inputMessagesFilterChatPhotos'; const inputFilter = 'inputMessagesFilterChatPhotos';
let message: any = await appMessagesManager.getSearch({ if(!message) {
message = await appMessagesManager.getSearch({
peerId, peerId,
inputFilter: {_: inputFilter}, inputFilter: {_: inputFilter},
maxId, maxId: 0,
limit: 2, limit: 1
backLimit: 1
}).then(value => { }).then(value => {
//console.log(lol); //console.log(lol);
// ! by descend // ! by descend
return value.history[0]; return value.history[0];
}); });
if(!middleware()) {
return;
}
}
if(message) { if(message) {
// ! гений в деле, костылируем (но это гениально) // ! гений в деле, костылируем (но это гениально)
const messagePhoto = message.action.photo; const messagePhoto = message.action.photo;
if(messagePhoto.id !== photo.id) { if(messagePhoto.id !== photo.id) {
message = { if(!hadMessage) {
_: 'message', message = appMessagesManager.generateFakeAvatarMessage(peerId, photo);
mid: maxId, } else {
media: {
_: 'messageMediaPhoto',
photo: photo
},
peerId,
date: (photo as Photo.photo).date,
fromId: peerId
};
appMessagesManager.getMessagesStorage(peerId)[maxId] = message; }
} }
const good = Array.from(this.querySelectorAll('img')).find(img => !img.classList.contains('emoji')); const f = (arr: typeof prevTargets) => arr.map(el => ({
element: el.element,
mid: (el.item as Message.messageService).mid,
peerId: (el.item as Message.messageService).peerId
}));
new AppMediaViewer() new AppMediaViewer()
.setSearchContext({ .setSearchContext({
peerId, peerId,
inputFilter, inputFilter,
}) })
.openMedia(message, good ? this : null); .openMedia(message, getTarget(), undefined, undefined, prevTargets ? f(prevTargets) : undefined, nextTargets ? f(nextTargets) : undefined);
loading = false;
return; return;
} }
} }
if(photo) { if(photo) {
const good = Array.from(this.querySelectorAll('img')).find(img => !img.classList.contains('emoji')); new AppMediaViewerAvatar(peerId).openMedia(photo.id, getTarget());
new AppMediaViewerAvatar(peerId).openMedia(photo.id, good ? this : null); }
}
export default class AvatarElement extends HTMLElement {
private peerId: number;
private isDialog = false;
public peerTitle: string;
public loadPromises: Promise<any>[];
//public lazyLoadQueue: LazyLoadQueueIntersector;
//private addedToQueue = false;
constructor() {
super();
// элемент создан
} }
connectedCallback() {
// браузер вызывает этот метод при добавлении элемента в документ
// (может вызываться много раз, если элемент многократно добавляется/удаляется)
this.isDialog = !!this.getAttribute('dialog');
if(this.getAttribute('clickable') === '') {
this.setAttribute('clickable', 'set');
let loading = false;
attachClickEvent(this, async(e) => {
cancelEvent(e);
if(loading) return;
//console.log('avatar clicked');
const peerId = this.peerId;
loading = true;
await openAvatarViewer(this, this.peerId, () => this.peerId === peerId);
loading = false; loading = false;
}); });
} }

35
src/components/chat/bubbles.ts

@ -4,7 +4,7 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager"; import type { AppImManager } from "../../lib/appManagers/appImManager";
import type { AppMessagesManager, HistoryResult, MyMessage } from "../../lib/appManagers/appMessagesManager"; import type { AppMessagesManager, HistoryResult, MyMessage } from "../../lib/appManagers/appMessagesManager";
import type { AppStickersManager } from "../../lib/appManagers/appStickersManager"; import type { AppStickersManager } from "../../lib/appManagers/appStickersManager";
import type { AppUsersManager } from "../../lib/appManagers/appUsersManager"; import type { AppUsersManager } from "../../lib/appManagers/appUsersManager";
@ -14,6 +14,7 @@ import type { AppDocsManager } from "../../lib/appManagers/appDocsManager";
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager"; import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
import type sessionStorage from '../../lib/sessionStorage'; import type sessionStorage from '../../lib/sessionStorage';
import type Chat from "./chat"; import type Chat from "./chat";
import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
import { cancelEvent, whichChild, getElementByPoint, attachClickEvent, positionElementByIndex, reflowScrollableElement } from "../../helpers/dom"; import { cancelEvent, whichChild, getElementByPoint, attachClickEvent, positionElementByIndex, reflowScrollableElement } from "../../helpers/dom";
import { getObjectKeysAndSort } from "../../helpers/object"; import { getObjectKeysAndSort } from "../../helpers/object";
import { isTouchSupported } from "../../helpers/touchSupport"; import { isTouchSupported } from "../../helpers/touchSupport";
@ -28,7 +29,6 @@ import ProgressivePreloader from "../preloader";
import Scrollable from "../scrollable"; import Scrollable from "../scrollable";
import StickyIntersector from "../stickyIntersector"; import StickyIntersector from "../stickyIntersector";
import animationIntersector from "../animationIntersector"; import animationIntersector from "../animationIntersector";
import { months } from "../../helpers/date";
import RichTextProcessor from "../../lib/richtextprocessor"; import RichTextProcessor from "../../lib/richtextprocessor";
import mediaSizes from "../../helpers/mediaSizes"; import mediaSizes from "../../helpers/mediaSizes";
import { isAndroid, isApple, isSafari } from "../../helpers/userAgent"; import { isAndroid, isApple, isSafari } from "../../helpers/userAgent";
@ -1415,15 +1415,15 @@ export default class ChatBubbles {
topMessage = 0; topMessage = 0;
} }
let readMaxId = 0;//, savedPosition: ReturnType<AppImManager['getChatSavedPosition']>; let readMaxId = 0, savedPosition: ReturnType<AppImManager['getChatSavedPosition']>;
if(!isTarget) { if(!isTarget) {
/* if(!samePeer) { if(!samePeer) {
savedPosition = this.chat.appImManager.getChatSavedPosition(this.chat); savedPosition = this.chat.appImManager.getChatSavedPosition(this.chat);
} }
if(savedPosition) { if(savedPosition) {
lastMsgId = savedPosition.mid;
} else */if(topMessage) { } else if(topMessage) {
readMaxId = this.appMessagesManager.getReadMaxIdIfUnread(peerId, this.chat.threadId); readMaxId = this.appMessagesManager.getReadMaxIdIfUnread(peerId, this.chat.threadId);
if(/* dialog.unread_count */readMaxId && !samePeer) { if(/* dialog.unread_count */readMaxId && !samePeer) {
lastMsgId = readMaxId; lastMsgId = readMaxId;
@ -1497,7 +1497,19 @@ export default class ChatBubbles {
this.lazyLoadQueue.lock(); this.lazyLoadQueue.lock();
const {promise, cached} = this.getHistory(lastMsgId, true, isJump, additionMsgId); let result: ReturnType<ChatBubbles['getHistory']>;
if(!savedPosition) {
result = this.getHistory(lastMsgId, true, isJump, additionMsgId);
} else {
result = {
promise: getHeavyAnimationPromise().then(() => {
return this.performHistoryResult(savedPosition.mids, true, false, undefined);
}) as any,
cached: true
};
}
const {promise, cached} = result;
// clear // clear
if(!cached) { if(!cached) {
@ -1533,8 +1545,9 @@ export default class ChatBubbles {
this.lazyLoadQueue.unlock(); this.lazyLoadQueue.unlock();
//if(dialog && lastMsgID && lastMsgID !== topMessage && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) { //if(dialog && lastMsgID && lastMsgID !== topMessage && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) {
/* if(savedPosition) { if(savedPosition) {
const mountedByLastMsgId = this.getMountedBubble(lastMsgId); this.scrollable.scrollTop = savedPosition.top;
/* const mountedByLastMsgId = this.getMountedBubble(lastMsgId);
let bubble: HTMLElement = mountedByLastMsgId?.bubble; let bubble: HTMLElement = mountedByLastMsgId?.bubble;
if(!bubble?.parentElement) { if(!bubble?.parentElement) {
bubble = this.findNextMountedBubbleByMsgId(lastMsgId); bubble = this.findNextMountedBubbleByMsgId(lastMsgId);
@ -1544,8 +1557,8 @@ export default class ChatBubbles {
const top = bubble.getBoundingClientRect().top; const top = bubble.getBoundingClientRect().top;
const distance = savedPosition.top - top; const distance = savedPosition.top - top;
this.scrollable.scrollTop += distance; this.scrollable.scrollTop += distance;
} } */
} else */if((topMessage && isJump) || isTarget) { } else if((topMessage && isJump) || isTarget) {
const fromUp = maxBubbleId > 0 && (maxBubbleId < lastMsgId || lastMsgId < 0); const fromUp = maxBubbleId > 0 && (maxBubbleId < lastMsgId || lastMsgId < 0);
const followingUnread = readMaxId === lastMsgId && !isTarget; const followingUnread = readMaxId === lastMsgId && !isTarget;
if(!fromUp && samePeer) { if(!fromUp && samePeer) {

2
src/components/chat/chat.ts

@ -251,8 +251,10 @@ export default class Chat extends EventListenerBase<{
} }
}); });
if(!samePeer) {
appSidebarRight.sharedMediaTab.setLoadMutex(this.setPeerPromise); appSidebarRight.sharedMediaTab.setLoadMutex(this.setPeerPromise);
appSidebarRight.sharedMediaTab.loadSidebarMedia(true); appSidebarRight.sharedMediaTab.loadSidebarMedia(true);
}
/* this.setPeerPromise.then(() => { /* this.setPeerPromise.then(() => {
appSidebarRight.sharedMediaTab.loadSidebarMedia(false); appSidebarRight.sharedMediaTab.loadSidebarMedia(false);
}); */ }); */

9
src/components/chat/dragAndDrop.ts

@ -5,6 +5,7 @@
*/ */
import { generatePathData } from "../../helpers/dom"; import { generatePathData } from "../../helpers/dom";
import { i18n, LangPackKey } from "../../lib/langPack";
export default class ChatDragAndDrop { export default class ChatDragAndDrop {
container: HTMLDivElement; container: HTMLDivElement;
@ -14,8 +15,8 @@ export default class ChatDragAndDrop {
constructor(appendTo: HTMLElement, private options: { constructor(appendTo: HTMLElement, private options: {
icon: string, icon: string,
header: string, header: LangPackKey,
subtitle: string, subtitle: LangPackKey,
onDrop: (e: DragEvent) => void onDrop: (e: DragEvent) => void
}) { }) {
this.container = document.createElement('div'); this.container = document.createElement('div');
@ -35,11 +36,11 @@ export default class ChatDragAndDrop {
const dropHeader = document.createElement('div'); const dropHeader = document.createElement('div');
dropHeader.classList.add('drop-header'); dropHeader.classList.add('drop-header');
dropHeader.innerHTML = options.header;//'Drop files here to send them'; dropHeader.append(i18n(options.header));
const dropSubtitle = document.createElement('div'); const dropSubtitle = document.createElement('div');
dropSubtitle.classList.add('drop-subtitle'); dropSubtitle.classList.add('drop-subtitle');
dropSubtitle.innerHTML = options.subtitle;//'without compression'; dropSubtitle.append(i18n(options.subtitle));
this.svg.append(this.path); this.svg.append(this.path);
this.outlineWrapper.append(this.svg); this.outlineWrapper.append(this.svg);

6
src/components/chat/input.ts

@ -332,7 +332,9 @@ export default class ChatInput {
this.newMessageWrapper.append(...[this.btnToggleEmoticons, this.inputMessageContainer, this.btnScheduled, this.attachMenu, this.recordTimeEl, this.fileInput].filter(Boolean)); this.newMessageWrapper.append(...[this.btnToggleEmoticons, this.inputMessageContainer, this.btnScheduled, this.attachMenu, this.recordTimeEl, this.fileInput].filter(Boolean));
this.rowsWrapper.append(this.replyElements.container, this.newMessageWrapper); this.rowsWrapper.append(this.replyElements.container);
this.stickersHelper = new StickersHelper(this.rowsWrapper);
this.rowsWrapper.append(this.newMessageWrapper);
this.btnCancelRecord = ButtonIcon('delete danger btn-circle z-depth-1 btn-record-cancel'); this.btnCancelRecord = ButtonIcon('delete danger btn-circle z-depth-1 btn-record-cancel');
@ -389,8 +391,6 @@ export default class ChatInput {
} }
}, {passive: false, capture: true}); */ }, {passive: false, capture: true}); */
this.stickersHelper = new StickersHelper(this.rowsWrapper);
this.listenerSetter.add(rootScope, 'settings_updated', () => { this.listenerSetter.add(rootScope, 'settings_updated', () => {
if(this.stickersHelper) { if(this.stickersHelper) {
if(!rootScope.settings.stickers.suggest) { if(!rootScope.settings.stickers.suggest) {

2
src/components/chat/selection.ts

@ -179,11 +179,13 @@ export default class ChatSelection {
checkboxField.label.classList.add('bubble-select-checkbox'); checkboxField.label.classList.add('bubble-select-checkbox');
// * if it is a render of new message // * if it is a render of new message
if(this.isSelecting) { // ! avoid breaking animation on start
const mid = +bubble.dataset.mid; const mid = +bubble.dataset.mid;
if(this.selectedMids.has(mid) && (!isGrouped || this.isGroupedMidsSelected(mid))) { if(this.selectedMids.has(mid) && (!isGrouped || this.isGroupedMidsSelected(mid))) {
checkboxField.input.checked = true; checkboxField.input.checked = true;
bubble.classList.add('is-selected'); bubble.classList.add('is-selected');
} }
}
if(bubble.classList.contains('document-container')) { if(bubble.classList.contains('document-container')) {
bubble.querySelector('.document, audio-element').append(checkboxField.label); bubble.querySelector('.document, audio-element').append(checkboxField.label);

9
src/components/chat/stickersHelper.ts

@ -23,7 +23,10 @@ export default class StickersHelper {
private lastEmoticon = ''; private lastEmoticon = '';
constructor(private appendTo: HTMLElement) { constructor(private appendTo: HTMLElement) {
this.container = document.createElement('div');
this.container.classList.add('stickers-helper', 'z-depth-1');
this.appendTo.append(this.container);
} }
public checkEmoticon(emoticon: string) { public checkEmoticon(emoticon: string) {
@ -32,7 +35,9 @@ export default class StickersHelper {
if(this.lastEmoticon && !emoticon) { if(this.lastEmoticon && !emoticon) {
if(this.container) { if(this.container) {
SetTransition(this.container, 'is-visible', false, 200, () => { SetTransition(this.container, 'is-visible', false, 200, () => {
if(this.stickersContainer) {
this.stickersContainer.innerHTML = ''; this.stickersContainer.innerHTML = '';
}
}); });
} }
} }
@ -86,8 +91,6 @@ export default class StickersHelper {
} }
private init() { private init() {
this.container = document.createElement('div');
this.container.classList.add('stickers-helper', 'z-depth-1');
this.container.addEventListener('click', (e) => { this.container.addEventListener('click', (e) => {
if(!findUpClassName(e.target, 'super-sticker')) { if(!findUpClassName(e.target, 'super-sticker')) {
return; return;
@ -104,7 +107,5 @@ export default class StickersHelper {
this.scrollable = new Scrollable(this.container); this.scrollable = new Scrollable(this.container);
this.lazyLoadQueue = new LazyLoadQueue(); this.lazyLoadQueue = new LazyLoadQueue();
this.superStickerRenderer = new SuperStickerRenderer(this.lazyLoadQueue, CHAT_ANIMATION_GROUP); this.superStickerRenderer = new SuperStickerRenderer(this.lazyLoadQueue, CHAT_ANIMATION_GROUP);
this.appendTo.append(this.container);
} }
} }

6
src/components/chat/topbar.ts

@ -447,7 +447,8 @@ export default class ChatTopbar {
public setTitle(count?: number) { public setTitle(count?: number) {
let titleEl: HTMLElement; let titleEl: HTMLElement;
if(this.chat.type === 'pinned') { if(this.chat.type === 'pinned') {
titleEl = i18n('PinnedMessagesCount', [count]); if(count === undefined) titleEl = i18n('Loading');
else titleEl = i18n('PinnedMessagesCount', [count]);
if(count === undefined) { if(count === undefined) {
this.appMessagesManager.getSearchCounters(this.peerId, [{_: 'inputMessagesFilterPinned'}]).then(result => { this.appMessagesManager.getSearchCounters(this.peerId, [{_: 'inputMessagesFilterPinned'}]).then(result => {
@ -481,7 +482,8 @@ export default class ChatTopbar {
}); });
} }
} else if(this.chat.type === 'discussion') { } else if(this.chat.type === 'discussion') {
titleEl = i18n('Chat.Title.Comments', [count]); if(count === undefined) titleEl = i18n('Loading');
else titleEl = i18n('Chat.Title.Comments', [count]);
if(count === undefined) { if(count === undefined) {
Promise.all([ Promise.all([

64
src/components/emoticonsDropdown/tabs/emoji.ts

@ -10,6 +10,7 @@ import { fastRaf } from "../../../helpers/schedulers";
import appImManager from "../../../lib/appManagers/appImManager"; import appImManager from "../../../lib/appManagers/appImManager";
import appStateManager from "../../../lib/appManagers/appStateManager"; import appStateManager from "../../../lib/appManagers/appStateManager";
import Config from "../../../lib/config"; import Config from "../../../lib/config";
import { i18n, LangPackKey } from "../../../lib/langPack";
import { RichTextProcessor } from "../../../lib/richtextprocessor"; import { RichTextProcessor } from "../../../lib/richtextprocessor";
import rootScope from "../../../lib/rootScope"; import rootScope from "../../../lib/rootScope";
import { putPreloader } from "../../misc"; import { putPreloader } from "../../misc";
@ -25,19 +26,32 @@ export default class EmojiTab implements EmoticonsTab {
private scroll: Scrollable; private scroll: Scrollable;
private stickyIntersector: StickyIntersector; private stickyIntersector: StickyIntersector;
private loadedURLs: Set<string> = new Set();
init() { init() {
this.content = document.getElementById('content-emoji') as HTMLDivElement; this.content = document.getElementById('content-emoji') as HTMLDivElement;
const categories = ["Smileys & Emotion", "Animals & Nature", "Food & Drink", "Travel & Places", "Activities", "Objects", /* "Symbols", */"Flags", "Skin Tones"]; const categories: LangPackKey[] = [
'Emoji.SmilesAndPeople',
'Emoji.AnimalsAndNature',
'Emoji.FoodAndDrink',
'Emoji.TravelAndPlaces',
'Emoji.ActivityAndSport',
'Emoji.Objects',
/* 'Emoji.Symbols', */
'Emoji.Flags',
'Skin Tones' as any
];
const divs: { const divs: {
[category: string]: HTMLDivElement [category in LangPackKey]?: HTMLDivElement
} = {}; } = {};
const sorted: { const sorted: Map<LangPackKey, string[]> = new Map([
[category: string]: string[] [
} = { 'Emoji.Recent',
'Recent': [] []
}; ]
]);
for(const emoji in Config.Emoji) { for(const emoji in Config.Emoji) {
const details = Config.Emoji[emoji]; const details = Config.Emoji[emoji];
@ -45,32 +59,35 @@ export default class EmojiTab implements EmoticonsTab {
const category = categories[+i[0] - 1]; const category = categories[+i[0] - 1];
if(!category) continue; // maybe it's skin tones if(!category) continue; // maybe it's skin tones
if(!sorted[category]) sorted[category] = []; let s = sorted.get(category);
sorted[category][+i.slice(1) || 0] = emoji; if(!s) {
s = [];
sorted.set(category, s);
}
s[+i.slice(1) || 0] = emoji;
} }
//console.log('emoticons sorted:', sorted); //console.log('emoticons sorted:', sorted);
//Object.keys(sorted).forEach(c => sorted[c].sort((a, b) => a - b)); //Object.keys(sorted).forEach(c => sorted[c].sort((a, b) => a - b));
categories.pop(); sorted.delete(categories.pop());
delete sorted["Skin Tones"];
//console.time('emojiParse'); //console.time('emojiParse');
for(const category in sorted) { sorted.forEach((emojis, category) => {
const div = document.createElement('div'); const div = document.createElement('div');
div.classList.add('emoji-category'); div.classList.add('emoji-category');
const titleDiv = document.createElement('div'); const titleDiv = document.createElement('div');
titleDiv.classList.add('category-title'); titleDiv.classList.add('category-title');
titleDiv.innerText = category; titleDiv.append(i18n(category));
const itemsDiv = document.createElement('div'); const itemsDiv = document.createElement('div');
itemsDiv.classList.add('category-items'); itemsDiv.classList.add('category-items');
div.append(titleDiv, itemsDiv); div.append(titleDiv, itemsDiv);
const emojis = sorted[category];
emojis.forEach(emoji => { emojis.forEach(emoji => {
/* if(emojiUnicode(emoji) === '1f481-200d-2642') { /* if(emojiUnicode(emoji) === '1f481-200d-2642') {
console.log('append emoji', emoji, emojiUnicode(emoji)); console.log('append emoji', emoji, emojiUnicode(emoji));
@ -86,7 +103,8 @@ export default class EmojiTab implements EmoticonsTab {
}); });
divs[category] = div; divs[category] = div;
} });
//console.timeEnd('emojiParse'); //console.timeEnd('emojiParse');
const menu = this.content.previousElementSibling as HTMLElement; const menu = this.content.previousElementSibling as HTMLElement;
@ -107,14 +125,14 @@ export default class EmojiTab implements EmoticonsTab {
]).then(() => { ]).then(() => {
preloader.remove(); preloader.remove();
this.recentItemsDiv = divs['Recent'].querySelector('.category-items'); this.recentItemsDiv = divs['Emoji.Recent'].querySelector('.category-items');
for(const emoji of this.recent) { for(const emoji of this.recent) {
this.appendEmoji(emoji, this.recentItemsDiv); this.appendEmoji(emoji, this.recentItemsDiv);
} }
this.recentItemsDiv.parentElement.classList.toggle('hide', !this.recent.length); this.recentItemsDiv.parentElement.classList.toggle('hide', !this.recent.length);
categories.unshift('Recent'); categories.unshift('Emoji.Recent');
categories.map(category => { categories.map(category => {
const div = divs[category]; const div = divs[category];
@ -174,6 +192,8 @@ export default class EmojiTab implements EmoticonsTab {
const image = spanEmoji.firstElementChild as HTMLImageElement; const image = spanEmoji.firstElementChild as HTMLImageElement;
image.setAttribute('loading', 'lazy'); image.setAttribute('loading', 'lazy');
const url = image.src;
if(!this.loadedURLs.has(url)) {
const placeholder = document.createElement('span'); const placeholder = document.createElement('span');
placeholder.classList.add('emoji-placeholder'); placeholder.classList.add('emoji-placeholder');
@ -190,11 +210,14 @@ export default class EmojiTab implements EmoticonsTab {
} }
spanEmoji.classList.remove('empty'); spanEmoji.classList.remove('empty');
this.loadedURLs.add(url);
}); });
}, {once: true}); }, {once: true});
spanEmoji.append(placeholder); spanEmoji.append(placeholder);
} }
}
//spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement; //spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement;
//spanEmoji.setAttribute('emoji', emoji); //spanEmoji.setAttribute('emoji', emoji);
@ -216,7 +239,12 @@ export default class EmojiTab implements EmoticonsTab {
//if(target.tagName !== 'SPAN') return; //if(target.tagName !== 'SPAN') return;
if(target.tagName === 'SPAN' && !target.classList.contains('emoji')) { if(target.tagName === 'SPAN' && !target.classList.contains('emoji')) {
target = findUpClassName(target, 'category-item').firstChild as HTMLElement; target = findUpClassName(target, 'category-item');
if(!target) {
return;
}
target = target.firstChild as HTMLElement;
} else if(target.tagName === 'DIV') return; } else if(target.tagName === 'DIV') return;
//console.log('contentEmoji div', target); //console.log('contentEmoji div', target);

5
src/components/popups/newMedia.ts

@ -5,7 +5,6 @@
*/ */
import type Chat from "../chat/chat"; import type Chat from "../chat/chat";
import { isTouchSupported } from "../../helpers/touchSupport";
import { calcImageInBox, placeCaretAtEnd, isSendShortcutPressed } from "../../helpers/dom"; import { calcImageInBox, placeCaretAtEnd, isSendShortcutPressed } from "../../helpers/dom";
import InputField from "../inputField"; import InputField from "../inputField";
import PopupElement from "."; import PopupElement from ".";
@ -14,7 +13,7 @@ import { toast } from "../toast";
import { prepareAlbum, wrapDocument } from "../wrappers"; import { prepareAlbum, wrapDocument } from "../wrappers";
import CheckboxField from "../checkboxField"; import CheckboxField from "../checkboxField";
import SendContextMenu from "../chat/sendContextMenu"; import SendContextMenu from "../chat/sendContextMenu";
import { createPosterForVideo, createPosterFromVideo, onVideoLoad } from "../../helpers/files"; import { createPosterFromVideo, onVideoLoad } from "../../helpers/files";
import { MyDocument } from "../../lib/appManagers/appDocsManager"; import { MyDocument } from "../../lib/appManagers/appDocsManager";
import I18n, { i18n, LangPackKey } from "../../lib/langPack"; import I18n, { i18n, LangPackKey } from "../../lib/langPack";
@ -50,7 +49,7 @@ export default class PopupNewMedia extends PopupElement {
inputField: InputField; inputField: InputField;
constructor(private chat: Chat, files: File[], willAttachType: PopupNewMedia['willAttach']['type']) { constructor(private chat: Chat, files: File[], willAttachType: PopupNewMedia['willAttach']['type']) {
super('popup-send-photo popup-new-media', null, {closable: true, withConfirm: 'PreviewSender.Send'}); super('popup-send-photo popup-new-media', null, {closable: true, withConfirm: 'Modal.Send'});
this.willAttach.type = willAttachType; this.willAttach.type = willAttachType;

24
src/components/ripple.ts

@ -87,28 +87,18 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise<bool
} */ } */
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
let rect = r.getBoundingClientRect(); const rect = r.getBoundingClientRect();
elem.classList.add('c-ripple__circle'); elem.classList.add('c-ripple__circle');
let clickX = clientX - rect.left; const clickX = clientX - rect.left;
let clickY = clientY - rect.top; const clickY = clientY - rect.top;
let size: number, clickPos: number; const radius = Math.sqrt((Math.abs(clickY - rect.height / 2) + rect.height / 2) ** 2 + (Math.abs(clickX - rect.width / 2) + rect.width / 2) ** 2);
if(rect.width > rect.height) { const size = radius;
size = rect.width;
clickPos = clickX;
} else {
size = rect.height;
clickPos = clickY;
}
let offsetFromCenter = clickPos > (size / 2) ? size - clickPos : clickPos;
size = size - offsetFromCenter;
size *= 1.1;
// center of circle // center of circle
let x = clickX - size / 2; const x = clickX - size / 2;
let y = clickY - size / 2; const y = clickY - size / 2;
//console.log('ripple click', offsetFromCenter, size, clickX, clickY); //console.log('ripple click', offsetFromCenter, size, clickX, clickY);

24
src/components/row.ts

@ -31,7 +31,7 @@ export default class Row {
noCheckboxSubtitle: boolean, noCheckboxSubtitle: boolean,
title: string, title: string,
titleLangKey: LangPackKey, titleLangKey: LangPackKey,
titleRight: string, titleRight: string | HTMLElement,
clickable: boolean | ((e: Event) => void), clickable: boolean | ((e: Event) => void),
navigationTab: SliderSuperTab navigationTab: SliderSuperTab
}> = {}) { }> = {}) {
@ -45,6 +45,7 @@ export default class Row {
} else if(options.subtitleLangKey) { } else if(options.subtitleLangKey) {
this.subtitle.append(i18n(options.subtitleLangKey)); this.subtitle.append(i18n(options.subtitleLangKey));
} }
this.container.append(this.subtitle);
let havePadding = false; let havePadding = false;
if(options.radioField || options.checkboxField) { if(options.radioField || options.checkboxField) {
@ -56,9 +57,16 @@ export default class Row {
if(options.checkboxField) { if(options.checkboxField) {
this.checkboxField = options.checkboxField; this.checkboxField = options.checkboxField;
const isToggle = options.checkboxField.label.classList.contains('checkbox-field-toggle');
if(isToggle) {
this.container.classList.add('row-with-toggle');
options.titleRight = this.checkboxField.label;
} else {
this.container.append(this.checkboxField.label); this.container.append(this.checkboxField.label);
}
if(!options.noCheckboxSubtitle) { if(!options.noCheckboxSubtitle && !isToggle) {
this.checkboxField.input.addEventListener('change', () => { this.checkboxField.input.addEventListener('change', () => {
replaceContent(this.subtitle, i18n(this.checkboxField.input.checked ? 'Checkbox.Enabled' : 'Checkbox.Disabled')); replaceContent(this.subtitle, i18n(this.checkboxField.input.checked ? 'Checkbox.Enabled' : 'Checkbox.Disabled'));
}); });
@ -67,7 +75,8 @@ export default class Row {
const i = options.radioField || options.checkboxField; const i = options.radioField || options.checkboxField;
i.label.classList.add('disable-hover'); i.label.classList.add('disable-hover');
} else { }
if(options.title || options.titleLangKey) { if(options.title || options.titleLangKey) {
let c: HTMLElement; let c: HTMLElement;
if(options.titleRight) { if(options.titleRight) {
@ -90,7 +99,13 @@ export default class Row {
if(options.titleRight) { if(options.titleRight) {
const titleRight = document.createElement('div'); const titleRight = document.createElement('div');
titleRight.classList.add('row-title', 'row-title-right'); titleRight.classList.add('row-title', 'row-title-right');
if(typeof(options.titleRight) === 'string') {
titleRight.innerHTML = options.titleRight; titleRight.innerHTML = options.titleRight;
} else {
titleRight.append(options.titleRight);
}
c.append(titleRight); c.append(titleRight);
} }
} }
@ -100,7 +115,6 @@ export default class Row {
this.title.classList.add('tgico', 'tgico-' + options.icon); this.title.classList.add('tgico', 'tgico-' + options.icon);
this.container.classList.add('row-with-icon'); this.container.classList.add('row-with-icon');
} }
}
if(havePadding) { if(havePadding) {
this.container.classList.add('row-with-padding'); this.container.classList.add('row-with-padding');
@ -125,8 +139,6 @@ export default class Row {
this.container.prepend(this.container.lastElementChild); this.container.prepend(this.container.lastElementChild);
} */ } */
} }
this.container.append(this.subtitle);
} }

4
src/components/scrollable.ts

@ -8,6 +8,7 @@ import { isTouchSupported } from "../helpers/touchSupport";
import { logger, LogLevels } from "../lib/logger"; import { logger, LogLevels } from "../lib/logger";
import fastSmoothScroll, { FocusDirection } from "../helpers/fastSmoothScroll"; import fastSmoothScroll, { FocusDirection } from "../helpers/fastSmoothScroll";
import useHeavyAnimationCheck from "../hooks/useHeavyAnimationCheck"; import useHeavyAnimationCheck from "../hooks/useHeavyAnimationCheck";
import { cancelEvent } from "../helpers/dom";
/* /*
var el = $0; var el = $0;
var height = 0; var height = 0;
@ -250,10 +251,11 @@ export class ScrollableX extends ScrollableBase {
const scrollHorizontally = (e: any) => { const scrollHorizontally = (e: any) => {
if(!e.deltaX) { if(!e.deltaX) {
this.container!.scrollLeft += e.deltaY / 4; this.container!.scrollLeft += e.deltaY / 4;
cancelEvent(e);
} }
}; };
this.container.addEventListener('wheel', scrollHorizontally, {passive: true}); this.container.addEventListener('wheel', scrollHorizontally, {passive: false});
} }
} }
} }

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

@ -11,6 +11,7 @@ import appPhotosManager from "../../../lib/appManagers/appPhotosManager";
import rootScope from "../../../lib/rootScope"; import rootScope from "../../../lib/rootScope";
import InputSearch from "../../inputSearch"; import InputSearch from "../../inputSearch";
import { canFocus } from "../../../helpers/dom"; import { canFocus } from "../../../helpers/dom";
import { isMobile } from "../../../helpers/userAgent";
// TODO: поиск по людям глобальный, если не нашло в контактах никого // TODO: поиск по людям глобальный, если не нашло в контактах никого
@ -53,7 +54,7 @@ export default class AppContactsTab extends SliderSuperTab {
} }
onOpenAfterTimeout() { onOpenAfterTimeout() {
if(!canFocus(true)) return; if(isMobile || !canFocus(true)) return;
this.inputSearch.input.focus(); this.inputSearch.input.focus();
} }

77
src/components/sidebarLeft/tabs/language.ts

@ -7,12 +7,13 @@
import { SettingSection } from ".."; import { SettingSection } from "..";
import { randomLong } from "../../../helpers/random"; import { randomLong } from "../../../helpers/random";
import I18n from "../../../lib/langPack"; import I18n from "../../../lib/langPack";
import apiManager from "../../../lib/mtproto/mtprotoworker";
import RadioField from "../../radioField"; import RadioField from "../../radioField";
import Row, { RadioFormFromRows } from "../../row"; import Row, { RadioFormFromRows } from "../../row";
import { SliderSuperTab } from "../../slider" import { SliderSuperTab } from "../../slider"
export default class AppLanguageTab extends SliderSuperTab { export default class AppLanguageTab extends SliderSuperTab {
protected init() { protected async init() {
this.container.classList.add('language-container'); this.container.classList.add('language-container');
this.setTitle('Telegram.LanguageViewController'); this.setTitle('Telegram.LanguageViewController');
@ -20,76 +21,21 @@ export default class AppLanguageTab extends SliderSuperTab {
const radioRows: Map<string, Row> = new Map(); const radioRows: Map<string, Row> = new Map();
let r = [{ const promise = apiManager.invokeApiCacheable('langpack.getLanguages', {
code: 'en', lang_pack: 'macos'
text: 'English', }).then((languages) => {
subtitle: 'English'
}, {
code: 'be',
text: 'Belarusian',
subtitle: 'Беларуская'
}, {
code: 'ca',
text: 'Catalan',
subtitle: 'Català'
}, {
code: 'nl',
text: 'Dutch',
subtitle: 'Nederlands'
}, {
code: 'fr',
text: 'French',
subtitle: 'Français'
}, {
code: 'de',
text: 'German',
subtitle: 'Deutsch'
}, {
code: 'it',
text: 'Italian',
subtitle: 'Italiano'
}, {
code: 'ms',
text: 'Malay',
subtitle: 'Bahasa Melayu'
}, {
code: 'pl',
text: 'Polish',
subtitle: 'Polski'
}, {
code: 'pt',
text: 'Portuguese (Brazil)',
subtitle: 'Português (Brasil)'
}, {
code: 'ru',
text: 'Russian',
subtitle: 'Русский'
}, {
code: 'es',
text: 'Spanish',
subtitle: 'Español'
}, {
code: 'tr',
text: 'Turkish',
subtitle: 'Türkçe'
}, {
code: 'uk',
text: 'Ukrainian',
subtitle: 'Українська'
}];
const random = randomLong(); const random = randomLong();
r.forEach(({code, text, subtitle}) => { languages.forEach((language) => {
const row = new Row({ const row = new Row({
radioField: new RadioField({ radioField: new RadioField({
text, text: language.name,
name: random, name: random,
value: code value: language.lang_code
}), }),
subtitle subtitle: language.native_name
}); });
radioRows.set(code, row); radioRows.set(language.lang_code, row);
}); });
const form = RadioFormFromRows([...radioRows.values()], (value) => { const form = RadioFormFromRows([...radioRows.values()], (value) => {
@ -107,7 +53,10 @@ export default class AppLanguageTab extends SliderSuperTab {
}); });
section.content.append(form); section.content.append(form);
});
this.scrollable.append(section.container); this.scrollable.append(section.container);
return promise;
} }
} }

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

@ -5,18 +5,17 @@
*/ */
import appImManager from "../../../lib/appManagers/appImManager"; import appImManager from "../../../lib/appManagers/appImManager";
import appMessagesManager from "../../../lib/appManagers/appMessagesManager"; import appMessagesManager, { AppMessagesManager } from "../../../lib/appManagers/appMessagesManager";
import appPeersManager from "../../../lib/appManagers/appPeersManager"; import appPeersManager from "../../../lib/appManagers/appPeersManager";
import appProfileManager from "../../../lib/appManagers/appProfileManager"; import appProfileManager from "../../../lib/appManagers/appProfileManager";
import appUsersManager, { User } from "../../../lib/appManagers/appUsersManager"; import appUsersManager, { User } from "../../../lib/appManagers/appUsersManager";
import { logger } from "../../../lib/logger";
import { RichTextProcessor } from "../../../lib/richtextprocessor"; import { RichTextProcessor } from "../../../lib/richtextprocessor";
import rootScope from "../../../lib/rootScope"; import rootScope from "../../../lib/rootScope";
import AppSearchSuper, { SearchSuperType } from "../../appSearchSuper."; import AppSearchSuper, { SearchSuperType } from "../../appSearchSuper.";
import AvatarElement from "../../avatar"; import AvatarElement, { openAvatarViewer } from "../../avatar";
import SidebarSlider, { SliderSuperTab } from "../../slider"; import SidebarSlider, { SliderSuperTab } from "../../slider";
import CheckboxField from "../../checkboxField"; import CheckboxField from "../../checkboxField";
import { attachClickEvent, replaceContent, whichChild } from "../../../helpers/dom"; import { attachClickEvent, replaceContent, cancelEvent } from "../../../helpers/dom";
import appSidebarRight from ".."; import appSidebarRight from "..";
import { TransitionSlider } from "../../transition"; import { TransitionSlider } from "../../transition";
import appNotificationsManager from "../../../lib/appManagers/appNotificationsManager"; import appNotificationsManager from "../../../lib/appManagers/appNotificationsManager";
@ -25,7 +24,7 @@ import PeerTitle from "../../peerTitle";
import AppEditChannelTab from "./editChannel"; import AppEditChannelTab from "./editChannel";
import AppEditContactTab from "./editContact"; import AppEditContactTab from "./editContact";
import appChatsManager, { Channel } from "../../../lib/appManagers/appChatsManager"; import appChatsManager, { Channel } from "../../../lib/appManagers/appChatsManager";
import { Chat, UserProfilePhoto } from "../../../layer"; import { Chat, Message, MessageAction, ChatFull, Photo } from "../../../layer";
import Button from "../../button"; import Button from "../../button";
import ButtonIcon from "../../buttonIcon"; import ButtonIcon from "../../buttonIcon";
import I18n, { i18n, LangPackKey } from "../../../lib/langPack"; import I18n, { i18n, LangPackKey } from "../../../lib/langPack";
@ -43,6 +42,8 @@ import { MOUNT_CLASS_TO } from "../../../config/debug";
import AppAddMembersTab from "../../sidebarLeft/tabs/addMembers"; import AppAddMembersTab from "../../sidebarLeft/tabs/addMembers";
import PopupPickUser from "../../popups/pickUser"; import PopupPickUser from "../../popups/pickUser";
import PopupPeer from "../../popups/peer"; import PopupPeer from "../../popups/peer";
import Scrollable from "../../scrollable";
import { isTouchSupported } from "../../../helpers/touchSupport";
let setText = (text: string, row: Row) => { let setText = (text: string, row: Row) => {
fastRaf(() => { fastRaf(() => {
@ -166,21 +167,21 @@ class PeerProfileAvatars {
public static BASE_CLASS = 'profile-avatars'; public static BASE_CLASS = 'profile-avatars';
public container: HTMLElement; public container: HTMLElement;
public avatars: HTMLElement; public avatars: HTMLElement;
//public gradient: HTMLElement; public gradient: HTMLElement;
public info: HTMLElement; public info: HTMLElement;
public tabs: HTMLDivElement; public tabs: HTMLDivElement;
public listLoader: ListLoader<string>; public listLoader: ListLoader<string | Message.messageService>;
public peerId: number; public peerId: number;
constructor() { constructor(public scrollable: Scrollable) {
this.container = document.createElement('div'); this.container = document.createElement('div');
this.container.classList.add(PeerProfileAvatars.BASE_CLASS + '-container'); this.container.classList.add(PeerProfileAvatars.BASE_CLASS + '-container');
this.avatars = document.createElement('div'); this.avatars = document.createElement('div');
this.avatars.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatars'); this.avatars.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatars');
//this.gradient = document.createElement('div'); this.gradient = document.createElement('div');
//this.gradient.classList.add(PeerProfileAvatars.BASE_CLASS + '-gradient'); this.gradient.classList.add(PeerProfileAvatars.BASE_CLASS + '-gradient');
this.info = document.createElement('div'); this.info = document.createElement('div');
this.info.classList.add(PeerProfileAvatars.BASE_CLASS + '-info'); this.info.classList.add(PeerProfileAvatars.BASE_CLASS + '-info');
@ -188,20 +189,60 @@ class PeerProfileAvatars {
this.tabs = document.createElement('div'); this.tabs = document.createElement('div');
this.tabs.classList.add(PeerProfileAvatars.BASE_CLASS + '-tabs'); this.tabs.classList.add(PeerProfileAvatars.BASE_CLASS + '-tabs');
this.container.append(this.avatars, /* this.gradient, */this.info, this.tabs); this.container.append(this.avatars, this.gradient, this.info, this.tabs);
const checkScrollTop = () => {
if(this.scrollable.scrollTop !== 0) {
this.scrollable.scrollIntoViewNew(this.scrollable.container.firstElementChild as HTMLElement, 'start');
return false;
}
return true;
};
const SWITCH_ZONE = 1 / 3;
let cancel = false; let cancel = false;
attachClickEvent(this.container, (_e) => { let freeze = false;
attachClickEvent(this.container, async(_e) => {
if(freeze) {
cancelEvent(_e);
return;
}
if(cancel) { if(cancel) {
cancel = false; cancel = false;
return; return;
} }
if(!checkScrollTop()) {
return;
}
const rect = this.container.getBoundingClientRect(); const rect = this.container.getBoundingClientRect();
const e = (_e as TouchEvent).touches ? (_e as TouchEvent).touches[0] : _e as MouseEvent; const e = (_e as TouchEvent).touches ? (_e as TouchEvent).touches[0] : _e as MouseEvent;
const x = e.pageX; const x = e.pageX;
const clickX = x - rect.left;
if(clickX > (rect.width * SWITCH_ZONE) && clickX < (rect.width - rect.width * SWITCH_ZONE)) {
const peerId = this.peerId;
const targets: {element: HTMLElement, item: string | Message.messageService}[] = [];
this.listLoader.previous.concat(this.listLoader.current, this.listLoader.next).forEach((item, idx) => {
targets.push({
element: /* null */this.avatars.children[idx] as HTMLElement,
item
});
});
const prevTargets = targets.slice(0, this.listLoader.previous.length);
const nextTargets = targets.slice(this.listLoader.previous.length + 1);
const target = this.avatars.children[this.listLoader.previous.length] as HTMLElement;
freeze = true;
openAvatarViewer(target, peerId, () => peerId === this.peerId, this.listLoader.current, prevTargets, nextTargets);
freeze = false;
} else {
const centerX = rect.right - (rect.width / 2); const centerX = rect.right - (rect.width / 2);
const toRight = x > centerX; const toRight = x > centerX;
@ -209,8 +250,16 @@ class PeerProfileAvatars {
// fastRaf(() => { // fastRaf(() => {
this.listLoader.go(toRight ? 1 : -1); this.listLoader.go(toRight ? 1 : -1);
// }); // });
}
}); });
const cancelNextClick = () => {
cancel = true;
document.body.addEventListener(isTouchSupported ? 'touchend' : 'click', (e) => {
cancel = false;
}, {once: true});
};
let width = 0, x = 0, lastDiffX = 0, lastIndex = 0, minX = 0; let width = 0, x = 0, lastDiffX = 0, lastIndex = 0, minX = 0;
const swipeHandler = new SwipeHandler({ const swipeHandler = new SwipeHandler({
element: this.avatars, element: this.avatars,
@ -225,7 +274,11 @@ class PeerProfileAvatars {
return false; return false;
}, },
verifyTouchTarget: (e) => { verifyTouchTarget: (e) => {
if(this.tabs.classList.contains('hide')) { if(!checkScrollTop()) {
cancelNextClick();
cancelEvent(e);
return false;
} else if(this.tabs.classList.contains('hide') || freeze) {
return false; return false;
} }
@ -247,7 +300,7 @@ class PeerProfileAvatars {
}, },
onReset: () => { onReset: () => {
const addIndex = Math.ceil(Math.abs(lastDiffX) / (width / 2)) * (lastDiffX >= 0 ? 1 : -1); const addIndex = Math.ceil(Math.abs(lastDiffX) / (width / 2)) * (lastDiffX >= 0 ? 1 : -1);
cancel = true; cancelNextClick();
//console.log(addIndex); //console.log(addIndex);
@ -268,15 +321,51 @@ class PeerProfileAvatars {
} }
const loadCount = 50; const loadCount = 50;
const listLoader: PeerProfileAvatars['listLoader'] = this.listLoader = new ListLoader<string>({ const listLoader: PeerProfileAvatars['listLoader'] = this.listLoader = new ListLoader<string | Message.messageService>({
loadCount, loadCount,
loadMore: (anchor, older) => { loadMore: (anchor, older) => {
return appPhotosManager.getUserPhotos(peerId, anchor || listLoader.current, loadCount).then(result => { if(peerId > 0) {
return appPhotosManager.getUserPhotos(peerId, (anchor || listLoader.current) as any, loadCount).then(result => {
return { return {
count: result.count, count: result.count,
items: result.photos items: result.photos
}; };
}); });
} else {
const promises: [Promise<ChatFull>, ReturnType<AppMessagesManager['getSearch']>] = [] as any;
if(!listLoader.current) {
promises.push(appProfileManager.getChatFull(peerId));
}
promises.push(appMessagesManager.getSearch({
peerId,
maxId: Number.MAX_SAFE_INTEGER,
inputFilter: {
_: 'inputMessagesFilterChatPhotos'
},
limit: loadCount,
backLimit: 0
}));
return Promise.all(promises).then((result) => {
const value = result.pop() as typeof result[1];
if(!listLoader.current) {
const chatFull = result[0];
const message = value.history.findAndSplice(m => {
return ((m as Message.messageService).action as MessageAction.messageActionChannelEditPhoto).photo.id === chatFull.chat_photo.id;
}) as Message.messageService;
listLoader.current = message || appMessagesManager.generateFakeAvatarMessage(this.peerId, chatFull.chat_photo);
}
//console.log('avatars loaded:', value);
return {
count: value.count,
items: value.history
};
});
}
}, },
processItem: this.processItem, processItem: this.processItem,
onJump: (item, older) => { onJump: (item, older) => {
@ -292,7 +381,10 @@ class PeerProfileAvatars {
} }
}); });
listLoader.current = (photo as UserProfilePhoto.userProfilePhoto).photo_id; if(photo._ === 'userProfilePhoto') {
listLoader.current = photo.photo_id;
}
this.processItem(listLoader.current); this.processItem(listLoader.current);
listLoader.load(true); listLoader.load(true);
@ -310,11 +402,17 @@ class PeerProfileAvatars {
this.tabs.classList.toggle('hide', this.tabs.childElementCount <= 1); this.tabs.classList.toggle('hide', this.tabs.childElementCount <= 1);
} }
public processItem = (photoId: string) => { public processItem = (photoId: string | Message.messageService) => {
const avatar = document.createElement('div'); const avatar = document.createElement('div');
avatar.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatar'); avatar.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatar');
const photo = appPhotosManager.getPhoto(photoId); let photo: Photo.photo;
if(photoId) {
photo = typeof(photoId) === 'string' ?
appPhotosManager.getPhoto(photoId) :
(photoId.action as MessageAction.messageActionChannelEditPhoto).photo as Photo.photo;
}
const img = new Image(); const img = new Image();
img.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatar-image'); img.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatar-image');
img.draggable = false; img.draggable = false;
@ -357,6 +455,10 @@ class PeerProfile {
private peerId = 0; private peerId = 0;
private threadId: number; private threadId: number;
constructor(public scrollable: Scrollable) {
}
public init() { public init() {
this.init = null; this.init = null;
@ -419,11 +521,17 @@ class PeerProfile {
}); });
this.notifications = new Row({ this.notifications = new Row({
checkboxField: new CheckboxField({text: 'Notifications'}) checkboxField: new CheckboxField({toggle: true}),
titleLangKey: 'Notifications',
icon: 'unmute'
}); });
this.section.content.append(this.phone.container, this.username.container, this.bio.container, this.notifications.container); this.section.content.append(this.phone.container, this.username.container, this.bio.container, this.notifications.container);
this.element.append(this.section.container);
const delimiter = document.createElement('div');
delimiter.classList.add('gradient-delimiter');
this.element.append(this.section.container, delimiter);
this.notifications.checkboxField.input.addEventListener('change', (e) => { this.notifications.checkboxField.input.addEventListener('change', (e) => {
if(!e.isTrusted) { if(!e.isTrusted) {
@ -502,7 +610,7 @@ class PeerProfile {
if(photo) { if(photo) {
const oldAvatars = this.avatars; const oldAvatars = this.avatars;
this.avatars = new PeerProfileAvatars(); this.avatars = new PeerProfileAvatars(this.scrollable);
this.avatars.setPeer(this.peerId); this.avatars.setPeer(this.peerId);
this.avatars.info.append(this.name, this.subtitle); this.avatars.info.append(this.name, this.subtitle);
@ -511,10 +619,14 @@ class PeerProfile {
if(oldAvatars) oldAvatars.container.replaceWith(this.avatars.container); if(oldAvatars) oldAvatars.container.replaceWith(this.avatars.container);
else this.element.prepend(this.avatars.container); else this.element.prepend(this.avatars.container);
this.scrollable.container.classList.add('parallax');
return; return;
} }
} }
this.scrollable.container.classList.remove('parallax');
if(this.avatars) { if(this.avatars) {
this.avatars.container.remove(); this.avatars.container.remove();
this.avatars = undefined; this.avatars = undefined;
@ -699,7 +811,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
// * body // * body
this.profile = new PeerProfile(); this.profile = new PeerProfile(this.scrollable);
this.profile.init(); this.profile.init();
this.scrollable.append(this.profile.element); this.scrollable.append(this.profile.element);
@ -712,6 +824,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
const top = rect.top; const top = rect.top;
const isSharedMedia = top <= HEADER_HEIGHT; const isSharedMedia = top <= HEADER_HEIGHT;
animatedCloseIcon.classList.toggle('state-back', isSharedMedia); animatedCloseIcon.classList.toggle('state-back', isSharedMedia);
this.searchSuper.container.classList.toggle('is-full-viewport', isSharedMedia);
transition(+isSharedMedia); transition(+isSharedMedia);
if(!isSharedMedia) { if(!isSharedMedia) {

8
src/components/sortedUserList.ts

@ -1,8 +1,14 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import appDialogsManager, { DialogDom } from "../lib/appManagers/appDialogsManager"; import appDialogsManager, { DialogDom } from "../lib/appManagers/appDialogsManager";
import { isInDOM, positionElementByIndex, replaceContent } from "../helpers/dom"; import { isInDOM, positionElementByIndex, replaceContent } from "../helpers/dom";
import { getHeavyAnimationPromise } from "../hooks/useHeavyAnimationCheck"; import { getHeavyAnimationPromise } from "../hooks/useHeavyAnimationCheck";
import appUsersManager from "../lib/appManagers/appUsersManager"; import appUsersManager from "../lib/appManagers/appUsersManager";
import { insertInDescendSortedArray, forEachReverse } from "../helpers/array"; import { insertInDescendSortedArray } from "../helpers/array";
type SortedUser = { type SortedUser = {
peerId: number, peerId: number,

4
src/components/swipeHandler.ts

@ -17,7 +17,7 @@ const attachGlobalListenerTo = window;
export default class SwipeHandler { export default class SwipeHandler {
private element: HTMLElement; private element: HTMLElement;
private onSwipe: (xDiff: number, yDiff: number) => boolean; private onSwipe: (xDiff: number, yDiff: number) => boolean;
private verifyTouchTarget: (evt: Touch | MouseEvent) => boolean; private verifyTouchTarget: (evt: TouchEvent | MouseEvent) => boolean;
private onFirstSwipe: () => void; private onFirstSwipe: () => void;
private onReset: () => void; private onReset: () => void;
@ -65,7 +65,7 @@ export default class SwipeHandler {
handleStart = (_e: TouchEvent | MouseEvent) => { handleStart = (_e: TouchEvent | MouseEvent) => {
const e = getEvent(_e); const e = getEvent(_e);
if(this.verifyTouchTarget && !this.verifyTouchTarget(e)) { if(this.verifyTouchTarget && !this.verifyTouchTarget(_e)) {
return this.reset(); return this.reset();
} }

4
src/components/wrappers.ts

@ -649,7 +649,7 @@ export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withT
}) { }) {
if(!((photo as MyPhoto).sizes || (photo as MyDocument).thumbs)) { if(!((photo as MyPhoto).sizes || (photo as MyDocument).thumbs)) {
if(boxWidth && boxHeight && photo._ === 'document') { if(boxWidth && boxHeight && photo._ === 'document') {
size = appPhotosManager.setAttachmentSize(photo, container, boxWidth, boxHeight); size = appPhotosManager.setAttachmentSize(photo, container, boxWidth, boxHeight, undefined, message && message.message);
} }
return { return {
@ -677,7 +677,7 @@ export function wrapPhoto({photo, message, container, boxWidth, boxHeight, withT
image = new Image(); image = new Image();
if(boxWidth && boxHeight) { // !album if(boxWidth && boxHeight) { // !album
size = appPhotosManager.setAttachmentSize(photo, container, boxWidth, boxHeight); size = appPhotosManager.setAttachmentSize(photo, container, boxWidth, boxHeight, undefined, message && message.message);
} }
const gotThumb = appPhotosManager.getStrippedThumbIfNeeded(photo); const gotThumb = appPhotosManager.getStrippedThumbIfNeeded(photo);

2
src/config/app.ts

@ -13,7 +13,7 @@ const App = {
id: 1025907, id: 1025907,
hash: '452b0359b988148995f22ff0f4229750', hash: '452b0359b988148995f22ff0f4229750',
version: '0.4.0', version: '0.4.0',
langPackVersion: '0.1.1', langPackVersion: '0.1.2',
langPack: 'macos', langPack: 'macos',
langPackCode: 'en', langPackCode: 'en',
domains: [] as string[], domains: [] as string[],

50
src/helpers/dom/getVisibleRect.ts

@ -0,0 +1,50 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
export default function getVisibleRect(element: HTMLElement, overflowElement: HTMLElement) {
const rect = element.getBoundingClientRect();
const overflowRect = overflowElement.getBoundingClientRect();
let {top: overflowTop, bottom: overflowBottom} = overflowRect;
// * respect sticky headers
const sticky = overflowElement.querySelector('.sticky');
if(sticky) {
const stickyRect = sticky.getBoundingClientRect();
overflowTop = stickyRect.bottom;
}
if(rect.top >= overflowBottom
|| rect.bottom <= overflowTop
|| rect.right <= overflowRect.left
|| rect.left >= overflowRect.right) {
return null;
}
const overflow = {
top: false,
right: false,
bottom: false,
left: false,
vertical: 0 as 0 | 1 | 2,
horizontal: 0 as 0 | 1 | 2
};
// @ts-ignore
const w: any = 'visualViewport' in window ? window.visualViewport : window;
const windowWidth = w.width || w.innerWidth;
const windowHeight = w.height || w.innerHeight;
return {
rect: {
top: rect.top < overflowTop && overflowTop !== 0 ? (overflow.top = true, ++overflow.vertical, overflowTop) : rect.top,
right: 0,
bottom: rect.bottom > overflowBottom && overflowBottom !== windowHeight ? (overflow.bottom = true, ++overflow.vertical, overflowBottom) : rect.bottom,
left: 0
},
overflow
};
}

20
src/helpers/mediaSizes.ts

@ -37,15 +37,15 @@ class MediaSizes extends EventListenerBase<{
private sizes: {[k in 'desktop' | 'handhelds']: Sizes} = { private sizes: {[k in 'desktop' | 'handhelds']: Sizes} = {
handhelds: { handhelds: {
regular: { regular: {
width: 293, width: 270,
height: 293 height: 270
}, },
webpage: { webpage: {
width: 293, width: 270,
height: 213 height: 200
}, },
album: { album: {
width: 293, width: 270,
height: 0 height: 0
}, },
esgSticker: { esgSticker: {
@ -55,15 +55,15 @@ class MediaSizes extends EventListenerBase<{
}, },
desktop: { desktop: {
regular: { regular: {
width: 480, width: 400,
height: 480 height: 320
}, },
webpage: { webpage: {
width: 480, width: 400,
height: 400 height: 320
}, },
album: { album: {
width: 451, width: 420,
height: 0 height: 0
}, },
esgSticker: { esgSticker: {

15
src/index.ts

@ -7,6 +7,7 @@
import App from './config/app'; import App from './config/app';
import findUpClassName from './helpers/dom/findUpClassName'; import findUpClassName from './helpers/dom/findUpClassName';
import fixSafariStickyInput from './helpers/dom/fixSafariStickyInput'; import fixSafariStickyInput from './helpers/dom/fixSafariStickyInput';
import { isMobileSafari } from './helpers/userAgent';
import './materialize.scss'; import './materialize.scss';
import './scss/style.scss'; import './scss/style.scss';
import './scss/tgico.scss'; import './scss/tgico.scss';
@ -259,6 +260,20 @@ console.timeEnd('get storage1'); */
if(authState._ !== 'authStateSignedIn'/* || 1 === 1 */) { if(authState._ !== 'authStateSignedIn'/* || 1 === 1 */) {
console.log('Will mount auth page:', authState._, Date.now() / 1000); console.log('Will mount auth page:', authState._, Date.now() / 1000);
const el = document.getElementById('auth-pages');
if(el) {
const scrollable = el.querySelector('.scrollable');
if((!touchSupport.isTouchSupported || isMobileSafari)) {
scrollable.classList.add('no-scrollbar');
}
const placeholder = document.createElement('div');
placeholder.classList.add('auth-placeholder');
scrollable.prepend(placeholder);
scrollable.append(placeholder.cloneNode());
}
//langPromise.then(async() => { //langPromise.then(async() => {
switch(authState._) { switch(authState._) {
case 'authStateSignIn': case 'authStateSignIn':

16
src/lang.ts

@ -2,7 +2,7 @@ const lang = {
"Animations": "Animations", "Animations": "Animations",
"AttachAlbum": "Album", "AttachAlbum": "Album",
"BlockModal.Search.Placeholder": "Block user...", "BlockModal.Search.Placeholder": "Block user...",
"DarkMode": "Sith Mode", "DarkMode": "Dark Mode",
"FilterIncludeExcludeInfo": "Choose chats and types of chats that will\nappear and never appear in this folder.", "FilterIncludeExcludeInfo": "Choose chats and types of chats that will\nappear and never appear in this folder.",
"FilterNameInputLabel": "Folder Name", "FilterNameInputLabel": "Folder Name",
"FilterMenuDelete": "Delete Folder", "FilterMenuDelete": "Delete Folder",
@ -59,7 +59,6 @@ const lang = {
"Checkbox.Disabled": "Disabled", "Checkbox.Disabled": "Disabled",
"Error.PreviewSender.CaptionTooLong": "Caption is too long.", "Error.PreviewSender.CaptionTooLong": "Caption is too long.",
"PreviewSender.GroupItems": "Group items", "PreviewSender.GroupItems": "Group items",
"PreviewSender.Send": "SEND",
"PreviewSender.SendAlbum": { "PreviewSender.SendAlbum": {
"one_value": "Send Album", "one_value": "Send Album",
"other_value": "Send %d Albums" "other_value": "Send %d Albums"
@ -407,6 +406,9 @@ const lang = {
"Chat.Date.ScheduledFor": "Scheduled for %@", "Chat.Date.ScheduledFor": "Scheduled for %@",
//"Chat.Date.ScheduledUntilOnline": "Scheduled until online", //"Chat.Date.ScheduledUntilOnline": "Scheduled until online",
"Chat.Date.ScheduledForToday": "Scheduled for today", "Chat.Date.ScheduledForToday": "Scheduled for today",
"Chat.DropTitle": "Drop files here to send them",
"Chat.DropQuickDesc": "in a quick way",
"Chat.DropAsFilesDesc": "without compression",
"Chat.Service.PeerJoinedTelegram": "%@ joined Telegram", "Chat.Service.PeerJoinedTelegram": "%@ joined Telegram",
"Chat.Service.Channel.UpdatedTitle": "Channel renamed to \"%@\"", "Chat.Service.Channel.UpdatedTitle": "Channel renamed to \"%@\"",
"Chat.Service.Channel.UpdatedPhoto": "Channel photo updated", "Chat.Service.Channel.UpdatedPhoto": "Channel photo updated",
@ -487,11 +489,21 @@ const lang = {
"EditAccount.Username": "Username", "EditAccount.Username": "Username",
"EditAccount.Title": "Edit Profile", "EditAccount.Title": "Edit Profile",
"EditAccount.Logout": "Log Out", "EditAccount.Logout": "Log Out",
"Emoji.Recent": "Frequently Used",
"Emoji.SmilesAndPeople": "Smileys & People",
"Emoji.AnimalsAndNature": "Animals & Nature",
"Emoji.FoodAndDrink": "Food & Drink",
"Emoji.ActivityAndSport": "Activity & Sport",
"Emoji.TravelAndPlaces": "Travel & Places",
"Emoji.Objects": "Objects",
//"Emoji.Symbols": "Symbols",
"Emoji.Flags": "Flags",
"LastSeen.HoursAgo": { "LastSeen.HoursAgo": {
"one_value": "last seen %d hour ago", "one_value": "last seen %d hour ago",
"other_value": "last seen %d hours ago" "other_value": "last seen %d hours ago"
}, },
"Login.Register.LastName.Placeholder": "Last Name", "Login.Register.LastName.Placeholder": "Last Name",
"Modal.Send": "Send",
"Telegram.GeneralSettingsViewController": "General Settings", "Telegram.GeneralSettingsViewController": "General Settings",
"Telegram.InstalledStickerPacksController": "Stickers", "Telegram.InstalledStickerPacksController": "Stickers",
"Telegram.NotificationSettingsViewController": "Notifications", "Telegram.NotificationSettingsViewController": "Notifications",

48
src/lib/appManagers/appImManager.ts

@ -7,7 +7,7 @@
//import apiManager from '../mtproto/apiManager'; //import apiManager from '../mtproto/apiManager';
import animationIntersector from '../../components/animationIntersector'; import animationIntersector from '../../components/animationIntersector';
import appSidebarLeft, { LEFT_COLUMN_ACTIVE_CLASSNAME } from "../../components/sidebarLeft"; import appSidebarLeft, { LEFT_COLUMN_ACTIVE_CLASSNAME } from "../../components/sidebarLeft";
import appSidebarRight, { AppSidebarRight, RIGHT_COLUMN_ACTIVE_CLASSNAME } from '../../components/sidebarRight'; import appSidebarRight, { RIGHT_COLUMN_ACTIVE_CLASSNAME } from '../../components/sidebarRight';
import mediaSizes, { ScreenSize } from '../../helpers/mediaSizes'; import mediaSizes, { ScreenSize } from '../../helpers/mediaSizes';
import { logger, LogLevels } from "../logger"; import { logger, LogLevels } from "../logger";
import apiManager from '../mtproto/mtprotoworker'; import apiManager from '../mtproto/mtprotoworker';
@ -46,6 +46,7 @@ import AppPrivateSearchTab from '../../components/sidebarRight/tabs/search';
import { i18n } from '../langPack'; import { i18n } from '../langPack';
import { SendMessageAction } from '../../layer'; import { SendMessageAction } from '../../layer';
import { highlightningColor } from '../../helpers/color'; import { highlightningColor } from '../../helpers/color';
import { getObjectKeysAndSort } from '../../helpers/object';
//console.log('appImManager included33!'); //console.log('appImManager included33!');
@ -54,6 +55,11 @@ appSidebarLeft; // just to include
export const CHAT_ANIMATION_GROUP = 'chat'; export const CHAT_ANIMATION_GROUP = 'chat';
const FOCUS_EVENT_NAME = isTouchSupported ? 'touchstart' : 'mousemove'; const FOCUS_EVENT_NAME = isTouchSupported ? 'touchstart' : 'mousemove';
export type ChatSavedPosition = {
mids: number[],
top: number
};
export class AppImManager { export class AppImManager {
public columnEl = document.getElementById('column-center') as HTMLDivElement; public columnEl = document.getElementById('column-center') as HTMLDivElement;
public chatsContainer: HTMLElement; public chatsContainer: HTMLElement;
@ -165,13 +171,13 @@ export class AppImManager {
this.setInnerPeer(peerId); this.setInnerPeer(peerId);
}); });
/* rootScope.on('peer_changing', (chat) => { rootScope.on('peer_changing', (chat) => {
this.saveChatPosition(chat); this.saveChatPosition(chat);
}); });
sessionStorage.get('chatPositions').then((c) => { sessionStorage.get('chatPositions').then((c) => {
sessionStorage.setToCache('chatPositions', c || {}); sessionStorage.setToCache('chatPositions', c || {});
}); */ });
} }
private onHashChange = () => { private onHashChange = () => {
@ -230,28 +236,34 @@ export class AppImManager {
}); });
} }
/* public saveChatPosition(chat: Chat) { public saveChatPosition(chat: Chat) {
const bubble = chat.bubbles.getBubbleByPoint('top'); if(!(['chat', 'discussion'] as ChatType[]).includes(chat.type) || !chat.peerId) {
if(bubble) { return;
const top = bubble.getBoundingClientRect().top; }
this.log('saving position by bubble:', bubble, top); //const bubble = chat.bubbles.getBubbleByPoint('top');
//if(bubble) {
//const top = bubble.getBoundingClientRect().top;
const top = chat.bubbles.scrollable.scrollTop;
const key = chat.peerId + (chat.threadId ? '_' + chat.threadId : ''); const key = chat.peerId + (chat.threadId ? '_' + chat.threadId : '');
const chatPositions = sessionStorage.getFromCache('chatPositions'); const chatPositions = sessionStorage.getFromCache('chatPositions');
chatPositions[key] = { chatPositions[key] = {
mid: +bubble.dataset.mid, mids: getObjectKeysAndSort(chat.bubbles.bubbles, 'desc'),
top top
}; } as ChatSavedPosition;
sessionStorage.set({chatPositions});
} this.log('saved chat position:', chatPositions[key]);
sessionStorage.set({chatPositions}, true);
//}
} }
public getChatSavedPosition(chat: Chat) { public getChatSavedPosition(chat: Chat): ChatSavedPosition {
const key = chat.peerId + (chat.threadId ? '_' + chat.threadId : ''); const key = chat.peerId + (chat.threadId ? '_' + chat.threadId : '');
return sessionStorage.getFromCache('chatPositions')[key]; return sessionStorage.getFromCache('chatPositions')[key];
} */ }
private setSettings = () => { private setSettings = () => {
document.documentElement.style.setProperty('--messages-text-size', rootScope.settings.messagesTextSize + 'px'); document.documentElement.style.setProperty('--messages-text-size', rootScope.settings.messagesTextSize + 'px');
@ -439,8 +451,8 @@ export class AppImManager {
if(types.length || force) { if(types.length || force) {
drops.push(new ChatDragAndDrop(dropsContainer, { drops.push(new ChatDragAndDrop(dropsContainer, {
icon: 'dragfiles', icon: 'dragfiles',
header: 'Drop files here to send them', header: 'Chat.DropTitle',
subtitle: 'without compression', subtitle: 'Chat.DropAsFilesDesc',
onDrop: (e: DragEvent) => { onDrop: (e: DragEvent) => {
toggle(e, false); toggle(e, false);
appImManager.log('drop', e); appImManager.log('drop', e);
@ -452,8 +464,8 @@ export class AppImManager {
if((foundMedia && !foundDocuments) || force) { if((foundMedia && !foundDocuments) || force) {
drops.push(new ChatDragAndDrop(dropsContainer, { drops.push(new ChatDragAndDrop(dropsContainer, {
icon: 'dragmedia', icon: 'dragmedia',
header: 'Drop files here to send them', header: 'Chat.DropTitle',
subtitle: 'in a quick way', subtitle: 'Chat.DropQuickDesc',
onDrop: (e: DragEvent) => { onDrop: (e: DragEvent) => {
toggle(e, false); toggle(e, false);
appImManager.log('drop', e); appImManager.log('drop', e);

20
src/lib/appManagers/appMessagesManager.ts

@ -17,7 +17,7 @@ import { createPosterForVideo } from "../../helpers/files";
import { copy, defineNotNumerableProperties, getObjectKeysAndSort } from "../../helpers/object"; import { copy, defineNotNumerableProperties, getObjectKeysAndSort } from "../../helpers/object";
import { randomLong } from "../../helpers/random"; import { randomLong } from "../../helpers/random";
import { splitStringByLength, limitSymbols, escapeRegExp } from "../../helpers/string"; import { splitStringByLength, limitSymbols, escapeRegExp } from "../../helpers/string";
import { Chat, ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageMedia, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, NotifyPeer, PeerNotifySettings, PhotoSize, SendMessageAction, Update } from "../../layer"; import { Chat, ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageMedia, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, NotifyPeer, PeerNotifySettings, PhotoSize, SendMessageAction, Update, Photo } from "../../layer";
import { InvokeApiOptions } from "../../types"; import { InvokeApiOptions } from "../../types";
import I18n, { i18n, join, langPack, LangPackKey, _i18n } from "../langPack"; import I18n, { i18n, join, langPack, LangPackKey, _i18n } from "../langPack";
import { logger, LogLevels } from "../logger"; import { logger, LogLevels } from "../logger";
@ -1548,6 +1548,24 @@ export class AppMessagesManager {
return fwdHeader; return fwdHeader;
} }
public generateFakeAvatarMessage(peerId: number, photo: Photo) {
const maxId = Number.MAX_SAFE_INTEGER;
const message = {
_: 'messageService',
action: {
_: 'messageActionChannelEditPhoto',
photo
},
mid: maxId,
peerId,
date: (photo as Photo.photo).date,
fromId: peerId
} as Message.messageService;
this.getMessagesStorage(peerId)[maxId] = message;
return message;
}
public setDialogTopMessage(message: MyMessage, dialog: MTDialog.dialog = this.getDialogByPeerId(message.peerId)[0]) { public setDialogTopMessage(message: MyMessage, dialog: MTDialog.dialog = this.getDialogByPeerId(message.peerId)[0]) {
if(dialog) { if(dialog) {
dialog.top_message = message.mid; dialog.top_message = message.mid;

11
src/lib/appManagers/appPhotosManager.ts

@ -219,7 +219,7 @@ export class AppPhotosManager {
return {image, loadPromise}; return {image, loadPromise};
} }
public setAttachmentSize(photo: MyPhoto | MyDocument, element: HTMLElement | SVGForeignObjectElement, boxWidth: number, boxHeight: number, noZoom = true) { public setAttachmentSize(photo: MyPhoto | MyDocument, element: HTMLElement | SVGForeignObjectElement, boxWidth: number, boxHeight: number, noZoom = true, hasText?: boolean) {
const photoSize = this.choosePhotoSize(photo, boxWidth, boxHeight); const photoSize = this.choosePhotoSize(photo, boxWidth, boxHeight);
//console.log('setAttachmentSize', photo, photo.sizes[0].bytes, div); //console.log('setAttachmentSize', photo, photo.sizes[0].bytes, div);
@ -233,7 +233,12 @@ export class AppPhotosManager {
height = 'h' in photoSize ? photoSize.h : 100; height = 'h' in photoSize ? photoSize.h : 100;
} }
const {w, h} = calcImageInBox(width, height, boxWidth, boxHeight, noZoom); let {w, h} = calcImageInBox(width, height, boxWidth, boxHeight, noZoom);
/* if(hasText) {
w = Math.max(boxWidth, w);
} */
if(element instanceof SVGForeignObjectElement) { if(element instanceof SVGForeignObjectElement) {
element.setAttributeNS(null, 'width', '' + w); element.setAttributeNS(null, 'width', '' + w);
element.setAttributeNS(null, 'height', '' + h); element.setAttributeNS(null, 'height', '' + h);
@ -257,7 +262,7 @@ export class AppPhotosManager {
} }
const sizes = (photo as MyPhoto).sizes || (photo as MyDocument).thumbs; const sizes = (photo as MyPhoto).sizes || (photo as MyDocument).thumbs;
const thumb = sizes?.length ? sizes[0] : null; const thumb = sizes?.length ? sizes.find(size => size._ === 'photoStrippedSize') : null;
if(thumb && ('bytes' in thumb)) { if(thumb && ('bytes' in thumb)) {
return appPhotosManager.getImageFromStrippedThumb(thumb as any); return appPhotosManager.getImageFromStrippedThumb(thumb as any);
} }

8
src/lib/sessionStorage.ts

@ -4,9 +4,10 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
import type { ChatSavedPosition } from './appManagers/appImManager';
import type { State } from './appManagers/appStateManager';
import { MOUNT_CLASS_TO } from '../config/debug'; import { MOUNT_CLASS_TO } from '../config/debug';
import { LangPackDifference } from '../layer'; import { LangPackDifference } from '../layer';
import type { State } from './appManagers/appStateManager';
import AppStorage from './storage'; import AppStorage from './storage';
const sessionStorage = new AppStorage<{ const sessionStorage = new AppStorage<{
@ -21,10 +22,7 @@ const sessionStorage = new AppStorage<{
server_time_offset: number, server_time_offset: number,
chatPositions: { chatPositions: {
[peerId_threadId: string]: { [peerId_threadId: string]: ChatSavedPosition
mid: number,
top: number
}
}, },
langPack: LangPackDifference langPack: LangPackDifference
} & State>({ } & State>({

3
src/pages/pageSignIn.ts

@ -12,7 +12,6 @@ import apiManager from "../lib/mtproto/mtprotoworker";
import { RichTextProcessor } from '../lib/richtextprocessor'; import { RichTextProcessor } from '../lib/richtextprocessor';
import { attachClickEvent, cancelEvent, replaceContent } from "../helpers/dom"; import { attachClickEvent, cancelEvent, replaceContent } from "../helpers/dom";
import Page from "./page"; import Page from "./page";
import pageAuthCode from "./pageAuthCode";
import InputField from "../components/inputField"; import InputField from "../components/inputField";
import CheckboxField from "../components/checkboxField"; import CheckboxField from "../components/checkboxField";
import Button from "../components/button"; import Button from "../components/button";
@ -341,7 +340,7 @@ let onFirstMount = () => {
}).then((code) => { }).then((code) => {
//console.log('got code', code); //console.log('got code', code);
pageAuthCode.mount(Object.assign(code, {phone_number: phone_number})); import('./pageAuthCode').then(m => m.default.mount(Object.assign(code, {phone_number: phone_number})));
}).catch(err => { }).catch(err => {
this.removeAttribute('disabled'); this.removeAttribute('disabled');

11
src/pages/pagesManager.ts

@ -9,6 +9,7 @@ import { whichChild } from "../helpers/dom";
import lottieLoader from "../lib/lottieLoader"; import lottieLoader from "../lib/lottieLoader";
import { horizontalMenu } from "../components/horizontalMenu"; import { horizontalMenu } from "../components/horizontalMenu";
import { MOUNT_CLASS_TO } from "../config/debug"; import { MOUNT_CLASS_TO } from "../config/debug";
import fastSmoothScroll from "../helpers/fastSmoothScroll";
class PagesManager { class PagesManager {
private pageId = -1; private pageId = -1;
@ -16,10 +17,12 @@ class PagesManager {
private selectTab: ReturnType<typeof horizontalMenu>; private selectTab: ReturnType<typeof horizontalMenu>;
public pagesDiv: HTMLDivElement; public pagesDiv: HTMLDivElement;
public scrollableDiv: HTMLElement;
constructor() { constructor() {
this.pagesDiv = document.getElementById('auth-pages') as HTMLDivElement; this.pagesDiv = document.getElementById('auth-pages') as HTMLDivElement;
this.selectTab = horizontalMenu(null, this.pagesDiv.firstElementChild.firstElementChild as HTMLDivElement, null, () => { this.scrollableDiv = this.pagesDiv.querySelector('.scrollable') as HTMLElement;
this.selectTab = horizontalMenu(null, this.scrollableDiv.querySelector('.tabs-container') as HTMLDivElement, null, () => {
if(this.page?.onShown) { if(this.page?.onShown) {
this.page.onShown(); this.page.onShown();
} }
@ -39,7 +42,13 @@ class PagesManager {
lottieLoader.loadLottieWorkers(); lottieLoader.loadLottieWorkers();
} }
this.pageId = id; this.pageId = id;
if(this.scrollableDiv) {
fastSmoothScroll(this.scrollableDiv, this.scrollableDiv.firstElementChild as HTMLElement, 'start');
}
} else { } else {
this.pagesDiv.style.display = 'none'; this.pagesDiv.style.display = 'none';
page.pageEl.style.display = ''; page.pageEl.style.display = '';

9
src/scss/partials/_avatar.scss

@ -7,8 +7,8 @@
avatar-element { avatar-element {
--size: 54px; --size: 54px;
--multiplier: 1; --multiplier: 1;
--color-top: var(--peer-avatar-blue-top); --color-top: var(--avatar-color-top);
--color-bottom: var(--peer-avatar-blue-bottom); --color-bottom: var(--avatar-color-bottom);
color: #fff; color: #fff;
width: var(--size); width: var(--size);
height: var(--size); height: var(--size);
@ -52,6 +52,11 @@ avatar-element {
--color-bottom: var(--peer-avatar-pink-bottom); --color-bottom: var(--peer-avatar-pink-bottom);
} }
&[data-color="blue"] {
--color-top: var(--peer-avatar-blue-top);
--color-bottom: var(--peer-avatar-blue-bottom);
}
&.tgico:before { &.tgico:before {
font-size: calc(32px / var(--multiplier)); font-size: calc(32px / var(--multiplier));
} }

43
src/scss/partials/_button.scss

@ -195,49 +195,6 @@
&-text { &-text {
flex: 1 1 auto; flex: 1 1 auto;
} }
.checkbox-field {
--size: 20px;
margin: 0 .3125rem;
padding: 0;
display: flex;
align-items: center;
pointer-events: none;
.checkbox-toggle {
--toggle-width: 1.9375rem;
width: var(--toggle-width);
height: .875rem;
background-color: var(--secondary-color);
border-radius: $border-radius-big;
position: relative;
display: flex;
align-items: center;
transition: background-color .2s;
&:before {
--offset: 3px;
width: 1.25rem;
height: 1.25rem;
border: 2px solid var(--secondary-color);
transition: border-color .2s, transform .2s;
background-color: var(--surface-color);
content: " ";
transform: translateX(calc(var(--offset) * -1));
border-radius: 50%;
position: absolute;
}
}
[type="checkbox"]:checked + .checkbox-toggle {
background-color: var(--primary-color);
&:before {
border-color: var(--primary-color);
transform: translateX(calc(var(--toggle-width) - 1.25rem + var(--offset)));
}
}
}
} }
/* &-overlay { /* &-overlay {

6
src/scss/partials/_chat.scss

@ -833,7 +833,7 @@ $chat-helper-size: 39px;
.btn-menu { .btn-menu {
padding: 8px 0; padding: 8px 0;
right: -11px; right: -11px;
bottom: calc(100% + 16px); bottom: calc(100% + 1.25rem);
> div { > div {
padding: 0 38px 0 16px; padding: 0 38px 0 16px;
@ -980,7 +980,7 @@ $chat-helper-size: 39px;
// } // }
// } // }
&.is-chat-input-hidden.is-selecting:not(.backwards) { .chat.type-chat &.is-chat-input-hidden.is-selecting:not(.backwards) {
--translateY: -78px; --translateY: -78px;
@include respond-to(handhelds) { @include respond-to(handhelds) {
@ -1130,7 +1130,7 @@ $chat-helper-size: 39px;
} }
} }
.bubbles.is-chat-input-hidden & { .chat.type-chat .bubbles.is-chat-input-hidden & {
margin-bottom: 1rem; // .25rem is eaten by the last bubble's margin-bottom margin-bottom: 1rem; // .25rem is eaten by the last bubble's margin-bottom
} }

11
src/scss/partials/_chatBubble.scss

@ -321,6 +321,17 @@ $bubble-margin: .25rem;
//background: rgba(0, 0, 0, .16); //background: rgba(0, 0, 0, .16);
background: var(--message-highlightning-color); background: var(--message-highlightning-color);
cursor: pointer; cursor: pointer;
html.no-touch & {
opacity: 0;
transition: opacity .2s ease-in-out;
}
}
@include hover() {
.bubble-beside-button {
opacity: 1;
}
} }
.forward { .forward {

5
src/scss/partials/_chatTopbar.scss

@ -89,7 +89,6 @@
font-size: 1rem; font-size: 1rem;
line-height: 24px; line-height: 24px;
max-width: calc(100% - 1.5rem); max-width: calc(100% - 1.5rem);
margin-bottom: 1px;
/* @include respond-to(handhelds) { /* @include respond-to(handhelds) {
text-overflow: ellipsis; text-overflow: ellipsis;
@ -109,6 +108,10 @@
line-height: var(--line-height); line-height: var(--line-height);
} }
.info:not(:empty) {
margin-top: 1px;
}
.btn-menu-toggle { .btn-menu-toggle {
.btn-menu { .btn-menu {
top: calc(100% + 7px); top: calc(100% + 7px);

4
src/scss/partials/_chatlist.scss

@ -236,6 +236,10 @@ ul.chatlist {
padding: .0625rem .4375rem .0625rem .5625rem; padding: .0625rem .4375rem .0625rem .5625rem;
} }
.dialog-avatar, .user-caption {
pointer-events: none;
}
.user-title { .user-title {
display: flex !important; display: flex !important;
align-items: center; align-items: center;

44
src/scss/partials/_checkbox.scss

@ -309,3 +309,47 @@
} }
} }
} }
.checkbox-field-toggle {
--size: 20px;
margin: 0 .3125rem;
padding: 0;
display: flex;
align-items: center;
pointer-events: none;
.checkbox-toggle {
--offset: 3px;
--toggle-width: 1.9375rem;
width: var(--toggle-width);
height: .875rem;
background-color: var(--secondary-color);
border-radius: $border-radius-big;
position: relative;
display: flex;
align-items: center;
transition: background-color .2s;
margin: 0 var(--offset);
&:before {
width: 1.25rem;
height: 1.25rem;
border: 2px solid var(--secondary-color);
transition: border-color .2s, transform .2s;
background-color: var(--surface-color);
content: " ";
transform: translateX(calc(var(--offset) * -1));
border-radius: 50%;
position: absolute;
}
}
[type="checkbox"]:checked + .checkbox-toggle {
background-color: var(--primary-color);
&:before {
border-color: var(--primary-color);
transform: translateX(calc(var(--toggle-width) - 1.25rem + var(--offset)));
}
}
}

4
src/scss/partials/_emojiDropdown.scss

@ -18,13 +18,13 @@
@include respond-to(esg-top) { @include respond-to(esg-top) {
position: absolute !important; position: absolute !important;
left: $chat-padding; left: $chat-padding;
bottom: 85px; bottom: 84px;
width: 420px !important; width: 420px !important;
height: 420px; height: 420px;
max-height: 420px; max-height: 420px;
box-shadow: 0px 5px 10px 5px rgba(16, 35, 47, .14); box-shadow: 0px 5px 10px 5px rgba(16, 35, 47, .14);
z-index: 3; z-index: 3;
border-radius: 10px; border-radius: $border-radius-medium;
transition: transform var(--esg-transition), opacity var(--esg-transition); transition: transform var(--esg-transition), opacity var(--esg-transition);
transform: scale(0); transform: scale(0);
opacity: 0; opacity: 0;

35
src/scss/partials/_leftSidebar.scss

@ -60,17 +60,17 @@
.menu-horizontal-scrollable { .menu-horizontal-scrollable {
z-index: 1; z-index: 1;
background-color: var(--surface-color); background-color: var(--surface-color);
border-bottom: 1px solid var(--border-color);
position: relative; position: relative;
box-shadow: 0px 1px 5px -1px rgba(0, 0, 0, .16); box-shadow: 0px 1px 5px -1px rgba(0, 0, 0, .16);
top: unset; top: unset;
height: 43px;
.scrollable { .scrollable {
position: relative; position: relative;
} }
.menu-horizontal-div { .menu-horizontal-div {
border-bottom: none; height: 43px;
position: relative !important; position: relative !important;
justify-content: flex-start; justify-content: flex-start;
@ -107,8 +107,8 @@
} }
&:not(.hide) + .scrollable { &:not(.hide) + .scrollable {
top: 44px; top: 43px;
height: calc(100% - 44px); height: calc(100% - 43px);
#folders-container { #folders-container {
margin-top: .5rem; margin-top: .5rem;
@ -116,13 +116,15 @@
} }
} }
.folders-tabs-scrollable .menu-horizontal-div-item:first-child { .folders-tabs-scrollable {
.menu-horizontal-div-item:first-child {
margin-left: .6875rem; margin-left: .6875rem;
@include respond-to(handhelds) { @include respond-to(handhelds) {
margin-left: .1875rem; margin-left: .1875rem;
} }
} }
}
.item-main { .item-main {
.input-search { .input-search {
@ -791,6 +793,12 @@
.sidebar-left { .sidebar-left {
&-section { &-section {
/* padding-bottom: .75rem;
@include respond-to(handhelds) {
padding-bottom: .5rem;
} */
padding: .5rem 0 1rem; padding: .5rem 0 1rem;
@include respond-to(handhelds) { @include respond-to(handhelds) {
@ -798,10 +806,8 @@
} }
&-content { &-content {
@include respond-to(not-handhelds) {
margin: 0 .5rem; margin: 0 .5rem;
@include respond-to(handhelds) {
margin: 0 .25rem;
} }
> .btn-primary { > .btn-primary {
@ -813,6 +819,13 @@
left: auto; left: auto;
} }
} }
@include respond-to(handhelds) {
> .checkbox-ripple,
> .btn-primary {
border-radius: 0;
}
}
} }
&-name { &-name {
@ -841,6 +854,7 @@
margin: 0; margin: 0;
} }
// * comment later
&:first-child:not(.no-delimiter) { &:first-child:not(.no-delimiter) {
padding-top: 0; padding-top: 0;
} }
@ -1020,8 +1034,6 @@
height: 66px; height: 66px;
padding-top: 9px; padding-top: 9px;
padding-bottom: 9px; padding-bottom: 9px;
border-radius: $border-radius-medium;
} }
.user-caption { .user-caption {
@ -1035,8 +1047,11 @@
ul { ul {
margin-top: .3125rem; margin-top: .3125rem;
@include respond-to(not-handhelds) {
padding: 0 .6875rem; padding: 0 .6875rem;
} }
}
} }
.notifications-container { .notifications-container {

8
src/scss/partials/_mediaViewer.scss

@ -282,11 +282,15 @@
} }
&.active { &.active {
transition: var(--open-duration) transform, var(--open-duration) border-radius; transition: transform var(--open-duration), border-radius var(--open-duration), opacity var(--open-duration) calc(var(--open-duration) / 8);
}
&.active.opening {
transition: transform var(--open-duration), border-radius var(--open-duration), opacity var(--open-duration) 0s;
} }
&.moving { &.moving {
transition: var(--move-duration) transform ease; transition: transform var(--move-duration) ease;
} }
/* &.center { /* &.center {

36
src/scss/partials/_profile.scss

@ -8,7 +8,8 @@
&-avatars { &-avatars {
&-container { &-container {
width: 100%; width: 100%;
height: 26.25rem; padding-bottom: 100%;
//height: 26.25rem;
//overflow: hidden; //overflow: hidden;
position: relative; position: relative;
cursor: pointer; cursor: pointer;
@ -23,14 +24,14 @@
} }
&-avatars { &-avatars {
width: inherit; width: 100%;
height: inherit; height: 100%;
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
transform: translateZ(-1px) scale(2); transform: translateZ(-1px) scale(2);
transform-origin: left top; transform-origin: left top;
transition: transform .2s ease-in-out; transition: transform .2s ease-in-out;
position: relative; position: absolute;
&:before { &:before {
content: " "; content: " ";
@ -57,14 +58,14 @@
} }
} }
/* &-gradient { &-gradient {
position: absolute; position: absolute;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
height: 80px; height: 80px;
background: linear-gradient(360deg, var(--secondary-color) 8.98%, rgba(0, 0, 0, 0) 100%); background: linear-gradient(360deg, rgba(0, 0, 0, .3) 8.98%, rgba(0, 0, 0, 0) 100%);
} */ }
&-info { &-info {
position: absolute; position: absolute;
@ -74,12 +75,17 @@
align-items: flex-start; align-items: flex-start;
left: 1.5rem; left: 1.5rem;
bottom: .5625rem; bottom: .5625rem;
pointer-events: none;
.profile-name, .profile-subtitle { .profile-name, .profile-subtitle {
color: #fff; color: #fff;
margin: 0; margin: 0;
} }
.peer-typing-text-dot {
background-color: #fff;
}
.profile-name { .profile-name {
margin-bottom: -1px; margin-bottom: -1px;
} }
@ -100,6 +106,7 @@
left: .375rem; left: .375rem;
right: .375rem; right: .375rem;
height: .125rem; height: .125rem;
pointer-events: none;
} }
&-tab { &-tab {
@ -152,8 +159,21 @@
position: relative; position: relative;
background-color: var(--surface-color); background-color: var(--surface-color);
//padding-top: .5625rem; //padding-top: .5625rem;
padding-bottom: 0; padding-bottom: .5rem;
//margin-bottom: .75rem;
//box-shadow: 0px 1px 5px -1px rgba(0, 0, 0, .16);
} }
/* .search-super {
&:before {
content: " ";
height: 12px;
width: 100%;
background-color: var(--background-color-true);
position: absolute;
top: -12px;
}
} */
} }
&-container { &-container {

44
src/scss/partials/_rightSidebar.scss

@ -100,6 +100,7 @@
} }
.shared-media-container { .shared-media-container {
//background-color: var(--background-color-true) !important;
/* .search-super { /* .search-super {
top: 100%; top: 100%;
min-height: calc((var(--vh, 1vh) * 100) - 100% - 56px); min-height: calc((var(--vh, 1vh) * 100) - 100% - 56px);
@ -109,8 +110,12 @@
} }
} */ } */
.scrollable { .scrollable {
perspective: 1px; perspective: 0px;
perspective-origin: left top; perspective-origin: left top;
&.parallax {
perspective: 1px;
}
} }
.search-super { .search-super {
@ -156,6 +161,23 @@
} }
} }
} }
.search-super {
.menu-horizontal-div-item {
height: 3.5rem;
padding-top: 0;
padding-bottom: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
line-height: var(--line-height);
}
.menu-horizontal-div i {
bottom: calc(-.625rem - 7px);
}
}
} }
.search-super { .search-super {
@ -197,10 +219,10 @@
flex: 1 1 auto; flex: 1 1 auto;
//margin-top: 36px; //margin-top: 36px;
i { /* i {
padding-right: 1.5rem !important; padding-right: 1.5rem !important;
margin-left: -.75rem !important; margin-left: -.75rem !important;
} } */
} }
&-tabs-scrollable { &-tabs-scrollable {
@ -211,16 +233,22 @@
top: 0px; top: 0px;
z-index: 2; z-index: 2;
background-color: var(--surface-color); background-color: var(--surface-color);
height: 3.5rem;
&:before { &:before {
position: absolute; position: absolute;
width: 100%; width: 100%;
height: 1px; height: 1px;
left: 0; left: 0;
top: -1px; top: 0;
background-color: inherit; background-color: inherit;
display: block; display: block;
content: " "; content: " ";
z-index: -1;
.search-super.is-full-viewport & {
top: -1px;
}
} }
.scrollable { .scrollable {
@ -336,16 +364,12 @@
&-content-media &-month { &-content-media &-month {
&-items { &-items {
width: 100%; width: 100%;
padding: 7.5px; padding-top: 1px;
display: grid; display: grid;
grid-template-columns: repeat(3,1fr); grid-template-columns: repeat(3,1fr);
grid-auto-rows: 1fr; grid-auto-rows: 1fr;
grid-gap: 3.5px; grid-gap: 1px;
@include respond-to(handhelds) {
padding: 7.5px 7.5px 7.5px 6.5px;
}
} }
} }

110
src/scss/partials/_row.scss

@ -0,0 +1,110 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
.row {
min-height: 3.5rem;
position: relative;
padding: .6875rem 1rem;
display: flex;
flex-direction: column;
justify-content: center;
a {
position: relative;
z-index: 1;
}
&-title-row {
display: flex;
justify-content: space-between;
align-items: center;
order: 0;
.row-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1 1 auto;
}
}
&-title {
color: var(--primary-text-color);
line-height: var(--line-height);
order: 0;
@include text-overflow(false);
&-right {
flex: 0 0 auto !important;
margin-left: 1rem;
}
}
&-midtitle {
font-size: .875rem;
order: 1;
}
&-with-padding {
padding-left: 4.5rem;
.row-title.tgico:before {
position: absolute;
left: 1rem;
font-size: 1.5rem;
color: var(--secondary-text-color);
pointer-events: none;
margin-top: -.125rem;
}
.row-subtitle:not(:empty) + .row-title.tgico:before {
margin-top: .25rem;
}
}
&-clickable {
cursor: pointer;
overflow: hidden;
@include respond-to(not-handhelds) {
border-radius: $border-radius-medium;
}
}
.radio-field-main, .checkbox-field {
padding-left: 3.375rem;
margin-left: -3.375rem;
}
.checkbox-field {
margin-right: 0;
height: auto;
.checkbox-caption {
padding-left: 0;
}
}
.checkbox-field-toggle {
margin: 0;
margin-right: .125rem;
padding: 0;
}
&-subtitle {
color: var(--secondary-text-color) !important;
font-size: .875rem !important;
line-height: var(--line-height);
margin-top: .125rem;
margin-bottom: .0625rem;
order: 1;
&:empty {
display: none;
}
}
}

23
src/scss/partials/_slider.scss

@ -4,6 +4,23 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
.menu-horizontal-scrollable {
&:after {
content: " ";
position: absolute;
height: 1px;
border-bottom: 1px solid var(--border-color);
bottom: 0;
left: 0;
right: 0;
z-index: -1;
}
.menu-horizontal-div {
border-bottom: none;
}
}
.menu-horizontal-div { .menu-horizontal-div {
width: 100%; width: 100%;
display: flex; display: flex;
@ -64,7 +81,7 @@
i { i {
position: absolute; position: absolute;
bottom: calc(-.625rem - 2px); bottom: calc(-.625rem - 3px);
left: 0; left: 0;
opacity: 0; opacity: 0;
background-color: var(--primary-color); background-color: var(--primary-color);
@ -72,8 +89,8 @@
width: 100%; width: 100%;
border-radius: .1875rem .1875rem 0 0; border-radius: .1875rem .1875rem 0 0;
pointer-events: none; pointer-events: none;
padding-right: .5rem; /* padding-right: .5rem;
margin-left: -.25rem; margin-left: -.25rem; */
box-sizing: content-box; box-sizing: content-box;
transform-origin: left; transform-origin: left;
z-index: 1; z-index: 1;

5
src/scss/partials/pages/_pages.scss

@ -43,8 +43,7 @@
flex-direction: column; flex-direction: column;
position: relative; position: relative;
&:before, &:after { .auth-placeholder {
content: " ";
flex: 1; flex: 1;
min-height: 3rem; min-height: 3rem;
/* height: 105px; */ /* height: 105px; */
@ -52,7 +51,7 @@
} }
@media screen and (max-height: 810px) { @media screen and (max-height: 810px) {
&:after { .auth-placeholder:last-child {
display: none; display: none;
} }
} }

7
src/scss/partials/popups/_mediaAttacher.scss

@ -64,13 +64,14 @@
position: relative; position: relative;
.btn-primary { .btn-primary {
width: 79px; width: auto;
height: 36px; height: 36px;
font-size: 14px; font-size: 14px;
font-weight: normal; font-weight: normal;
padding: 0; padding: 0 1.375rem;
margin-top: -3px; margin-top: -3px;
border-radius: $border-radius-medium; border-radius: $border-radius-medium;
text-transform: uppercase;
} }
} }
@ -81,7 +82,7 @@
&-title { &-title {
flex: 1; flex: 1;
padding: 0 2rem 0 1.5rem; padding-left: 1.5rem;
margin: 0; margin: 0;
margin-top: -3px; margin-top: -3px;
font-size: 1.25rem; font-size: 1.25rem;

115
src/scss/style.scss

@ -118,7 +118,7 @@ $chat-padding-handhelds: .5rem;
:root { :root {
// * Day theme // * Day theme
--body-background-color: #fff; --body-background-color: #fff;
//--background-color: #f4f4f5; --background-color-true: #f4f4f5;
--background-color: #fff; --background-color: #fff;
--border-color: #dfe1e5; --border-color: #dfe1e5;
--surface-color: #fff; --surface-color: #fff;
@ -136,6 +136,8 @@ $chat-padding-handhelds: .5rem;
@include splitColor(danger-color, #df3f40, true, false); @include splitColor(danger-color, #df3f40, true, false);
--avatar-online-color: #0ac630; --avatar-online-color: #0ac630;
--avatar-color-top: var(--peer-avatar-blue-top);
--avatar-color-bottom: var(--peer-avatar-blue-bottom);
--chatlist-status-color: var(--avatar-online-color); --chatlist-status-color: var(--avatar-online-color);
--chatlist-pinned-color: #a2abb2; --chatlist-pinned-color: #a2abb2;
--badge-text-color: #fff; --badge-text-color: #fff;
@ -159,7 +161,7 @@ html.night {
//:root { //:root {
// * Night theme // * Night theme
--body-background-color: #181818; --body-background-color: #181818;
//--background-color: #181818; --background-color-true: #181818;
--background-color: #212121; --background-color: #212121;
--border-color: #0f0f0f; --border-color: #0f0f0f;
--surface-color: #212121; --surface-color: #212121;
@ -177,6 +179,8 @@ html.night {
@include splitColor(danger-color, #ff595a, true, false); @include splitColor(danger-color, #ff595a, true, false);
--avatar-online-color: #0ac630; --avatar-online-color: #0ac630;
--avatar-color-top: var(--peer-avatar-violet-top);
--avatar-color-bottom: var(--peer-avatar-violet-bottom);
--chatlist-status-color: var(--primary-color); --chatlist-status-color: var(--primary-color);
--chatlist-pinned-color: var(--secondary-color); --chatlist-pinned-color: var(--secondary-color);
--badge-text-color: #fff; --badge-text-color: #fff;
@ -231,6 +235,7 @@ html.night {
@import "partials/peerTyping"; @import "partials/peerTyping";
@import "partials/poll"; @import "partials/poll";
@import "partials/transition"; @import "partials/transition";
@import "partials/row";
@import "partials/popups/popup"; @import "partials/popups/popup";
@import "partials/popups/editAvatar"; @import "partials/popups/editAvatar";
@ -1009,94 +1014,6 @@ middle-ellipsis-element {
} }
} }
.row {
min-height: 3.5rem;
position: relative;
padding: .6875rem 1rem;
display: flex;
flex-direction: column;
justify-content: center;
a {
position: relative;
z-index: 1;
}
&-title-row {
display: flex;
justify-content: space-between;
align-items: center;
.row-title {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1 1 auto;
}
}
&-title {
color: var(--primary-text-color);
line-height: var(--line-height);
@include text-overflow(false);
&-right {
flex: 0 0 auto !important;
margin-left: 1rem;
}
}
&-midtitle {
font-size: .875rem;
}
&-with-padding {
padding-left: 4.5rem;
.row-title.tgico:before {
position: absolute;
left: 1rem;
font-size: 1.5rem;
margin-top: .25rem;
color: var(--secondary-text-color);
pointer-events: none;
}
}
&-clickable {
cursor: pointer;
border-radius: $border-radius-medium;
overflow: hidden;
}
.radio-field-main, .checkbox-field {
padding-left: 3.375rem;
margin-left: -3.375rem;
}
.checkbox-field {
margin-right: 0;
height: auto;
.checkbox-caption {
padding-left: 0;
}
}
&-subtitle {
color: var(--secondary-text-color) !important;
font-size: .875rem !important;
line-height: var(--line-height);
margin-top: .125rem;
margin-bottom: .0625rem;
&:empty {
display: none;
}
}
}
.hover-effect { .hover-effect {
@include hover-background-effect(); @include hover-background-effect();
} }
@ -1193,3 +1110,21 @@ middle-ellipsis-element {
} }
} }
} }
.gradient-delimiter {
width: 100%;
height: .75rem;
display: flex;
background-color: var(--background-color-true);
position: relative;
&:before {
content: " ";
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
background: linear-gradient(180deg, rgba(0, 0, 0, .06) 0%, rgba(0, 0, 0, 0) 20%, rgba(0, 0, 0, 0) 94%, rgba(0, 0, 0, .06) 100%);
}
}

Loading…
Cancel
Save