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. 129
      src/components/avatar.ts
  4. 35
      src/components/chat/bubbles.ts
  5. 6
      src/components/chat/chat.ts
  6. 9
      src/components/chat/dragAndDrop.ts
  7. 6
      src/components/chat/input.ts
  8. 10
      src/components/chat/selection.ts
  9. 11
      src/components/chat/stickersHelper.ts
  10. 6
      src/components/chat/topbar.ts
  11. 104
      src/components/emoticonsDropdown/tabs/emoji.ts
  12. 5
      src/components/popups/newMedia.ts
  13. 24
      src/components/ripple.ts
  14. 76
      src/components/row.ts
  15. 4
      src/components/scrollable.ts
  16. 3
      src/components/sidebarLeft/tabs/contacts.ts
  17. 119
      src/components/sidebarLeft/tabs/language.ts
  18. 181
      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. 50
      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. 47
      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. 2
      src/scss/partials/_ripple.scss
  46. 110
      src/scss/partials/_row.scss
  47. 23
      src/scss/partials/_slider.scss
  48. 5
      src/scss/partials/pages/_pages.scss
  49. 7
      src/scss/partials/popups/_mediaAttacher.scss
  50. 115
      src/scss/style.scss

50
src/components/appMediaViewer.ts

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

4
src/components/appSearchSuper..ts

@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
*/
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 { escapeRegExp, limitSymbols } from "../helpers/string";
import appChatsManager from "../lib/appManagers/appChatsManager";
@ -124,7 +124,7 @@ export default class AppSearchSuper { @@ -124,7 +124,7 @@ export default class AppSearchSuper {
this.container.classList.add('search-super');
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);

129
src/components/avatar.ts

@ -9,7 +9,7 @@ import appProfileManager from "../lib/appManagers/appProfileManager"; @@ -9,7 +9,7 @@ import appProfileManager from "../lib/appManagers/appProfileManager";
import rootScope from "../lib/rootScope";
import { attachClickEvent, cancelEvent } from "../helpers/dom";
import AppMediaViewer, { AppMediaViewerAvatar } from "./appMediaViewer";
import { Photo } from "../layer";
import { Message, Photo } from "../layer";
import appPeersManager from "../lib/appManagers/appPeersManager";
//import type { LazyLoadQueueIntersector } from "./lazyLoadQueue";
@ -21,8 +21,72 @@ const onAvatarUpdate = (peerId: number) => { @@ -21,8 +21,72 @@ const onAvatarUpdate = (peerId: number) => {
});
};
rootScope.on('avatar_update', onAvatarUpdate);
rootScope.on('peer_title_edit', onAvatarUpdate);
rootScope.on('avatar_update', onAvatarUpdate);
rootScope.on('peer_title_edit', onAvatarUpdate);
export async function openAvatarViewer(target: HTMLElement, peerId: number, middleware: () => boolean, message?: any, prevTargets?: {element: HTMLElement, item: string | Message.messageService}[], nextTargets?: typeof prevTargets) {
const photo = await appProfileManager.getFullPhoto(peerId);
if(!middleware() || !photo) {
return;
}
const getTarget = () => {
const good = Array.from(target.querySelectorAll('img')).find(img => !img.classList.contains('emoji'));
return good ? target : null;
};
if(peerId < 0) {
const hadMessage = !!message;
const inputFilter = 'inputMessagesFilterChatPhotos';
if(!message) {
message = await appMessagesManager.getSearch({
peerId,
inputFilter: {_: inputFilter},
maxId: 0,
limit: 1
}).then(value => {
//console.log(lol);
// ! by descend
return value.history[0];
});
if(!middleware()) {
return;
}
}
if(message) {
// ! гений в деле, костылируем (но это гениально)
const messagePhoto = message.action.photo;
if(messagePhoto.id !== photo.id) {
if(!hadMessage) {
message = appMessagesManager.generateFakeAvatarMessage(peerId, photo);
} else {
}
}
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()
.setSearchContext({
peerId,
inputFilter,
})
.openMedia(message, getTarget(), undefined, undefined, prevTargets ? f(prevTargets) : undefined, nextTargets ? f(nextTargets) : undefined);
return;
}
}
if(photo) {
new AppMediaViewerAvatar(peerId).openMedia(photo.id, getTarget());
}
}
export default class AvatarElement extends HTMLElement {
private peerId: number;
@ -51,64 +115,7 @@ export default class AvatarElement extends HTMLElement { @@ -51,64 +115,7 @@ export default class AvatarElement extends HTMLElement {
//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;
}
if(peerId < 0) {
const maxId = Number.MAX_SAFE_INTEGER;
const inputFilter = 'inputMessagesFilterChatPhotos';
let message: any = await appMessagesManager.getSearch({
peerId,
inputFilter: {_: inputFilter},
maxId,
limit: 2,
backLimit: 1
}).then(value => {
//console.log(lol);
// ! by descend
return value.history[0];
});
if(message) {
// ! гений в деле, костылируем (но это гениально)
const messagePhoto = message.action.photo;
if(messagePhoto.id !== photo.id) {
message = {
_: 'message',
mid: maxId,
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'));
new AppMediaViewer()
.setSearchContext({
peerId,
inputFilter,
})
.openMedia(message, good ? this : null);
loading = false;
return;
}
}
if(photo) {
const good = Array.from(this.querySelectorAll('img')).find(img => !img.classList.contains('emoji'));
new AppMediaViewerAvatar(peerId).openMedia(photo.id, good ? this : null);
}
await openAvatarViewer(this, this.peerId, () => this.peerId === peerId);
loading = false;
});
}

35
src/components/chat/bubbles.ts

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

6
src/components/chat/chat.ts

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

9
src/components/chat/dragAndDrop.ts

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

6
src/components/chat/input.ts

@ -332,7 +332,9 @@ export default class ChatInput { @@ -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.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');
@ -389,8 +391,6 @@ export default class ChatInput { @@ -389,8 +391,6 @@ export default class ChatInput {
}
}, {passive: false, capture: true}); */
this.stickersHelper = new StickersHelper(this.rowsWrapper);
this.listenerSetter.add(rootScope, 'settings_updated', () => {
if(this.stickersHelper) {
if(!rootScope.settings.stickers.suggest) {

10
src/components/chat/selection.ts

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

11
src/components/chat/stickersHelper.ts

@ -23,7 +23,10 @@ export default class StickersHelper { @@ -23,7 +23,10 @@ export default class StickersHelper {
private lastEmoticon = '';
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) {
@ -32,7 +35,9 @@ export default class StickersHelper { @@ -32,7 +35,9 @@ export default class StickersHelper {
if(this.lastEmoticon && !emoticon) {
if(this.container) {
SetTransition(this.container, 'is-visible', false, 200, () => {
this.stickersContainer.innerHTML = '';
if(this.stickersContainer) {
this.stickersContainer.innerHTML = '';
}
});
}
}
@ -86,8 +91,6 @@ export default class StickersHelper { @@ -86,8 +91,6 @@ export default class StickersHelper {
}
private init() {
this.container = document.createElement('div');
this.container.classList.add('stickers-helper', 'z-depth-1');
this.container.addEventListener('click', (e) => {
if(!findUpClassName(e.target, 'super-sticker')) {
return;
@ -104,7 +107,5 @@ export default class StickersHelper { @@ -104,7 +107,5 @@ export default class StickersHelper {
this.scrollable = new Scrollable(this.container);
this.lazyLoadQueue = new LazyLoadQueue();
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 { @@ -447,7 +447,8 @@ export default class ChatTopbar {
public setTitle(count?: number) {
let titleEl: HTMLElement;
if(this.chat.type === 'pinned') {
titleEl = i18n('PinnedMessagesCount', [count]);
if(count === undefined) titleEl = i18n('Loading');
else titleEl = i18n('PinnedMessagesCount', [count]);
if(count === undefined) {
this.appMessagesManager.getSearchCounters(this.peerId, [{_: 'inputMessagesFilterPinned'}]).then(result => {
@ -481,7 +482,8 @@ export default class ChatTopbar { @@ -481,7 +482,8 @@ export default class ChatTopbar {
});
}
} 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) {
Promise.all([

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

@ -10,6 +10,7 @@ import { fastRaf } from "../../../helpers/schedulers"; @@ -10,6 +10,7 @@ import { fastRaf } from "../../../helpers/schedulers";
import appImManager from "../../../lib/appManagers/appImManager";
import appStateManager from "../../../lib/appManagers/appStateManager";
import Config from "../../../lib/config";
import { i18n, LangPackKey } from "../../../lib/langPack";
import { RichTextProcessor } from "../../../lib/richtextprocessor";
import rootScope from "../../../lib/rootScope";
import { putPreloader } from "../../misc";
@ -25,19 +26,32 @@ export default class EmojiTab implements EmoticonsTab { @@ -25,19 +26,32 @@ export default class EmojiTab implements EmoticonsTab {
private scroll: Scrollable;
private stickyIntersector: StickyIntersector;
private loadedURLs: Set<string> = new Set();
init() {
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: {
[category: string]: HTMLDivElement
[category in LangPackKey]?: HTMLDivElement
} = {};
const sorted: {
[category: string]: string[]
} = {
'Recent': []
};
const sorted: Map<LangPackKey, string[]> = new Map([
[
'Emoji.Recent',
[]
]
]);
for(const emoji in Config.Emoji) {
const details = Config.Emoji[emoji];
@ -45,32 +59,35 @@ export default class EmojiTab implements EmoticonsTab { @@ -45,32 +59,35 @@ export default class EmojiTab implements EmoticonsTab {
const category = categories[+i[0] - 1];
if(!category) continue; // maybe it's skin tones
if(!sorted[category]) sorted[category] = [];
sorted[category][+i.slice(1) || 0] = emoji;
let s = sorted.get(category);
if(!s) {
s = [];
sorted.set(category, s);
}
s[+i.slice(1) || 0] = emoji;
}
//console.log('emoticons sorted:', sorted);
//Object.keys(sorted).forEach(c => sorted[c].sort((a, b) => a - b));
categories.pop();
delete sorted["Skin Tones"];
sorted.delete(categories.pop());
//console.time('emojiParse');
for(const category in sorted) {
sorted.forEach((emojis, category) => {
const div = document.createElement('div');
div.classList.add('emoji-category');
const titleDiv = document.createElement('div');
titleDiv.classList.add('category-title');
titleDiv.innerText = category;
titleDiv.append(i18n(category));
const itemsDiv = document.createElement('div');
itemsDiv.classList.add('category-items');
div.append(titleDiv, itemsDiv);
const emojis = sorted[category];
emojis.forEach(emoji => {
/* if(emojiUnicode(emoji) === '1f481-200d-2642') {
console.log('append emoji', emoji, emojiUnicode(emoji));
@ -86,7 +103,8 @@ export default class EmojiTab implements EmoticonsTab { @@ -86,7 +103,8 @@ export default class EmojiTab implements EmoticonsTab {
});
divs[category] = div;
}
});
//console.timeEnd('emojiParse');
const menu = this.content.previousElementSibling as HTMLElement;
@ -107,14 +125,14 @@ export default class EmojiTab implements EmoticonsTab { @@ -107,14 +125,14 @@ export default class EmojiTab implements EmoticonsTab {
]).then(() => {
preloader.remove();
this.recentItemsDiv = divs['Recent'].querySelector('.category-items');
this.recentItemsDiv = divs['Emoji.Recent'].querySelector('.category-items');
for(const emoji of this.recent) {
this.appendEmoji(emoji, this.recentItemsDiv);
}
this.recentItemsDiv.parentElement.classList.toggle('hide', !this.recent.length);
categories.unshift('Recent');
categories.unshift('Emoji.Recent');
categories.map(category => {
const div = divs[category];
@ -173,27 +191,32 @@ export default class EmojiTab implements EmoticonsTab { @@ -173,27 +191,32 @@ export default class EmojiTab implements EmoticonsTab {
if(spanEmoji.firstElementChild && !RichTextProcessor.emojiSupported) {
const image = spanEmoji.firstElementChild as HTMLImageElement;
image.setAttribute('loading', 'lazy');
const placeholder = document.createElement('span');
placeholder.classList.add('emoji-placeholder');
if(rootScope.settings.animationsEnabled) {
image.style.opacity = '0';
placeholder.style.opacity = '1';
const url = image.src;
if(!this.loadedURLs.has(url)) {
const placeholder = document.createElement('span');
placeholder.classList.add('emoji-placeholder');
if(rootScope.settings.animationsEnabled) {
image.style.opacity = '0';
placeholder.style.opacity = '1';
}
image.addEventListener('load', () => {
fastRaf(() => {
if(rootScope.settings.animationsEnabled) {
image.style.opacity = '';
placeholder.style.opacity = '';
}
spanEmoji.classList.remove('empty');
this.loadedURLs.add(url);
});
}, {once: true});
spanEmoji.append(placeholder);
}
image.addEventListener('load', () => {
fastRaf(() => {
if(rootScope.settings.animationsEnabled) {
image.style.opacity = '';
placeholder.style.opacity = '';
}
spanEmoji.classList.remove('empty');
});
}, {once: true});
spanEmoji.append(placeholder);
}
//spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement;
@ -216,7 +239,12 @@ export default class EmojiTab implements EmoticonsTab { @@ -216,7 +239,12 @@ export default class EmojiTab implements EmoticonsTab {
//if(target.tagName !== 'SPAN') return;
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;
//console.log('contentEmoji div', target);
@ -253,4 +281,4 @@ export default class EmojiTab implements EmoticonsTab { @@ -253,4 +281,4 @@ export default class EmojiTab implements EmoticonsTab {
onClose() {
}
}
}

5
src/components/popups/newMedia.ts

@ -5,7 +5,6 @@ @@ -5,7 +5,6 @@
*/
import type Chat from "../chat/chat";
import { isTouchSupported } from "../../helpers/touchSupport";
import { calcImageInBox, placeCaretAtEnd, isSendShortcutPressed } from "../../helpers/dom";
import InputField from "../inputField";
import PopupElement from ".";
@ -14,7 +13,7 @@ import { toast } from "../toast"; @@ -14,7 +13,7 @@ import { toast } from "../toast";
import { prepareAlbum, wrapDocument } from "../wrappers";
import CheckboxField from "../checkboxField";
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 I18n, { i18n, LangPackKey } from "../../lib/langPack";
@ -50,7 +49,7 @@ export default class PopupNewMedia extends PopupElement { @@ -50,7 +49,7 @@ export default class PopupNewMedia extends PopupElement {
inputField: InputField;
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;

24
src/components/ripple.ts

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

76
src/components/row.ts

@ -31,7 +31,7 @@ export default class Row { @@ -31,7 +31,7 @@ export default class Row {
noCheckboxSubtitle: boolean,
title: string,
titleLangKey: LangPackKey,
titleRight: string,
titleRight: string | HTMLElement,
clickable: boolean | ((e: Event) => void),
navigationTab: SliderSuperTab
}> = {}) {
@ -45,6 +45,7 @@ export default class Row { @@ -45,6 +45,7 @@ export default class Row {
} else if(options.subtitleLangKey) {
this.subtitle.append(i18n(options.subtitleLangKey));
}
this.container.append(this.subtitle);
let havePadding = false;
if(options.radioField || options.checkboxField) {
@ -56,9 +57,16 @@ export default class Row { @@ -56,9 +57,16 @@ export default class Row {
if(options.checkboxField) {
this.checkboxField = options.checkboxField;
this.container.append(this.checkboxField.label);
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);
}
if(!options.noCheckboxSubtitle) {
if(!options.noCheckboxSubtitle && !isToggle) {
this.checkboxField.input.addEventListener('change', () => {
replaceContent(this.subtitle, i18n(this.checkboxField.input.checked ? 'Checkbox.Enabled' : 'Checkbox.Disabled'));
});
@ -67,41 +75,47 @@ export default class Row { @@ -67,41 +75,47 @@ export default class Row {
const i = options.radioField || options.checkboxField;
i.label.classList.add('disable-hover');
} else {
if(options.title || options.titleLangKey) {
let c: HTMLElement;
if(options.titleRight) {
c = document.createElement('div');
c.classList.add('row-title-row');
this.container.append(c);
} else {
c = this.container;
}
}
if(options.title || options.titleLangKey) {
let c: HTMLElement;
if(options.titleRight) {
c = document.createElement('div');
c.classList.add('row-title-row');
this.container.append(c);
} else {
c = this.container;
}
this.title = document.createElement('div');
this.title.classList.add('row-title');
if(options.title) {
this.title.innerHTML = options.title;
} else {
this.title.append(i18n(options.titleLangKey));
}
c.append(this.title);
this.title = document.createElement('div');
this.title.classList.add('row-title');
if(options.title) {
this.title.innerHTML = options.title;
} else {
this.title.append(i18n(options.titleLangKey));
}
c.append(this.title);
if(options.titleRight) {
const titleRight = document.createElement('div');
titleRight.classList.add('row-title', 'row-title-right');
if(options.titleRight) {
const titleRight = document.createElement('div');
titleRight.classList.add('row-title', 'row-title-right');
if(typeof(options.titleRight) === 'string') {
titleRight.innerHTML = options.titleRight;
c.append(titleRight);
} else {
titleRight.append(options.titleRight);
}
}
if(options.icon) {
havePadding = true;
this.title.classList.add('tgico', 'tgico-' + options.icon);
this.container.classList.add('row-with-icon');
c.append(titleRight);
}
}
if(options.icon) {
havePadding = true;
this.title.classList.add('tgico', 'tgico-' + options.icon);
this.container.classList.add('row-with-icon');
}
if(havePadding) {
this.container.classList.add('row-with-padding');
}
@ -125,8 +139,6 @@ export default class Row { @@ -125,8 +139,6 @@ export default class Row {
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"; @@ -8,6 +8,7 @@ import { isTouchSupported } from "../helpers/touchSupport";
import { logger, LogLevels } from "../lib/logger";
import fastSmoothScroll, { FocusDirection } from "../helpers/fastSmoothScroll";
import useHeavyAnimationCheck from "../hooks/useHeavyAnimationCheck";
import { cancelEvent } from "../helpers/dom";
/*
var el = $0;
var height = 0;
@ -250,10 +251,11 @@ export class ScrollableX extends ScrollableBase { @@ -250,10 +251,11 @@ export class ScrollableX extends ScrollableBase {
const scrollHorizontally = (e: any) => {
if(!e.deltaX) {
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"; @@ -11,6 +11,7 @@ import appPhotosManager from "../../../lib/appManagers/appPhotosManager";
import rootScope from "../../../lib/rootScope";
import InputSearch from "../../inputSearch";
import { canFocus } from "../../../helpers/dom";
import { isMobile } from "../../../helpers/userAgent";
// TODO: поиск по людям глобальный, если не нашло в контактах никого
@ -53,7 +54,7 @@ export default class AppContactsTab extends SliderSuperTab { @@ -53,7 +54,7 @@ export default class AppContactsTab extends SliderSuperTab {
}
onOpenAfterTimeout() {
if(!canFocus(true)) return;
if(isMobile || !canFocus(true)) return;
this.inputSearch.input.focus();
}

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

@ -7,12 +7,13 @@ @@ -7,12 +7,13 @@
import { SettingSection } from "..";
import { randomLong } from "../../../helpers/random";
import I18n from "../../../lib/langPack";
import apiManager from "../../../lib/mtproto/mtprotoworker";
import RadioField from "../../radioField";
import Row, { RadioFormFromRows } from "../../row";
import { SliderSuperTab } from "../../slider"
export default class AppLanguageTab extends SliderSuperTab {
protected init() {
protected async init() {
this.container.classList.add('language-container');
this.setTitle('Telegram.LanguageViewController');
@ -20,94 +21,42 @@ export default class AppLanguageTab extends SliderSuperTab { @@ -20,94 +21,42 @@ export default class AppLanguageTab extends SliderSuperTab {
const radioRows: Map<string, Row> = new Map();
let r = [{
code: 'en',
text: 'English',
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();
r.forEach(({code, text, subtitle}) => {
const row = new Row({
radioField: new RadioField({
text,
name: random,
value: code
}),
subtitle
const promise = apiManager.invokeApiCacheable('langpack.getLanguages', {
lang_pack: 'macos'
}).then((languages) => {
const random = randomLong();
languages.forEach((language) => {
const row = new Row({
radioField: new RadioField({
text: language.name,
name: random,
value: language.lang_code
}),
subtitle: language.native_name
});
radioRows.set(language.lang_code, row);
});
radioRows.set(code, row);
});
const form = RadioFormFromRows([...radioRows.values()], (value) => {
I18n.getLangPack(value);
});
I18n.getCacheLangPack().then(langPack => {
const row = radioRows.get(langPack.lang_code);
if(!row) {
console.error('no row', row, langPack);
return;
}
row.radioField.setValueSilently(true);
const form = RadioFormFromRows([...radioRows.values()], (value) => {
I18n.getLangPack(value);
});
I18n.getCacheLangPack().then(langPack => {
const row = radioRows.get(langPack.lang_code);
if(!row) {
console.error('no row', row, langPack);
return;
}
row.radioField.setValueSilently(true);
});
section.content.append(form);
});
section.content.append(form);
this.scrollable.append(section.container);
return promise;
}
}

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

@ -5,18 +5,17 @@ @@ -5,18 +5,17 @@
*/
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 appProfileManager from "../../../lib/appManagers/appProfileManager";
import appUsersManager, { User } from "../../../lib/appManagers/appUsersManager";
import { logger } from "../../../lib/logger";
import { RichTextProcessor } from "../../../lib/richtextprocessor";
import rootScope from "../../../lib/rootScope";
import AppSearchSuper, { SearchSuperType } from "../../appSearchSuper.";
import AvatarElement from "../../avatar";
import AvatarElement, { openAvatarViewer } from "../../avatar";
import SidebarSlider, { SliderSuperTab } from "../../slider";
import CheckboxField from "../../checkboxField";
import { attachClickEvent, replaceContent, whichChild } from "../../../helpers/dom";
import { attachClickEvent, replaceContent, cancelEvent } from "../../../helpers/dom";
import appSidebarRight from "..";
import { TransitionSlider } from "../../transition";
import appNotificationsManager from "../../../lib/appManagers/appNotificationsManager";
@ -25,7 +24,7 @@ import PeerTitle from "../../peerTitle"; @@ -25,7 +24,7 @@ import PeerTitle from "../../peerTitle";
import AppEditChannelTab from "./editChannel";
import AppEditContactTab from "./editContact";
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 ButtonIcon from "../../buttonIcon";
import I18n, { i18n, LangPackKey } from "../../../lib/langPack";
@ -43,6 +42,8 @@ import { MOUNT_CLASS_TO } from "../../../config/debug"; @@ -43,6 +42,8 @@ import { MOUNT_CLASS_TO } from "../../../config/debug";
import AppAddMembersTab from "../../sidebarLeft/tabs/addMembers";
import PopupPickUser from "../../popups/pickUser";
import PopupPeer from "../../popups/peer";
import Scrollable from "../../scrollable";
import { isTouchSupported } from "../../../helpers/touchSupport";
let setText = (text: string, row: Row) => {
fastRaf(() => {
@ -166,21 +167,21 @@ class PeerProfileAvatars { @@ -166,21 +167,21 @@ class PeerProfileAvatars {
public static BASE_CLASS = 'profile-avatars';
public container: HTMLElement;
public avatars: HTMLElement;
//public gradient: HTMLElement;
public gradient: HTMLElement;
public info: HTMLElement;
public tabs: HTMLDivElement;
public listLoader: ListLoader<string>;
public listLoader: ListLoader<string | Message.messageService>;
public peerId: number;
constructor() {
constructor(public scrollable: Scrollable) {
this.container = document.createElement('div');
this.container.classList.add(PeerProfileAvatars.BASE_CLASS + '-container');
this.avatars = document.createElement('div');
this.avatars.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatars');
//this.gradient = document.createElement('div');
//this.gradient.classList.add(PeerProfileAvatars.BASE_CLASS + '-gradient');
this.gradient = document.createElement('div');
this.gradient.classList.add(PeerProfileAvatars.BASE_CLASS + '-gradient');
this.info = document.createElement('div');
this.info.classList.add(PeerProfileAvatars.BASE_CLASS + '-info');
@ -188,29 +189,77 @@ class PeerProfileAvatars { @@ -188,29 +189,77 @@ class PeerProfileAvatars {
this.tabs = document.createElement('div');
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;
attachClickEvent(this.container, (_e) => {
let freeze = false;
attachClickEvent(this.container, async(_e) => {
if(freeze) {
cancelEvent(_e);
return;
}
if(cancel) {
cancel = false;
return;
}
if(!checkScrollTop()) {
return;
}
const rect = this.container.getBoundingClientRect();
const e = (_e as TouchEvent).touches ? (_e as TouchEvent).touches[0] : _e as MouseEvent;
const x = e.pageX;
const centerX = rect.right - (rect.width / 2);
const toRight = x > centerX;
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);
// this.avatars.classList.remove('no-transition');
// fastRaf(() => {
this.listLoader.go(toRight ? 1 : -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 toRight = x > centerX;
// this.avatars.classList.remove('no-transition');
// fastRaf(() => {
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;
const swipeHandler = new SwipeHandler({
element: this.avatars,
@ -225,7 +274,11 @@ class PeerProfileAvatars { @@ -225,7 +274,11 @@ class PeerProfileAvatars {
return false;
},
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;
}
@ -247,7 +300,7 @@ class PeerProfileAvatars { @@ -247,7 +300,7 @@ class PeerProfileAvatars {
},
onReset: () => {
const addIndex = Math.ceil(Math.abs(lastDiffX) / (width / 2)) * (lastDiffX >= 0 ? 1 : -1);
cancel = true;
cancelNextClick();
//console.log(addIndex);
@ -268,15 +321,51 @@ class PeerProfileAvatars { @@ -268,15 +321,51 @@ class PeerProfileAvatars {
}
const loadCount = 50;
const listLoader: PeerProfileAvatars['listLoader'] = this.listLoader = new ListLoader<string>({
const listLoader: PeerProfileAvatars['listLoader'] = this.listLoader = new ListLoader<string | Message.messageService>({
loadCount,
loadMore: (anchor, older) => {
return appPhotosManager.getUserPhotos(peerId, anchor || listLoader.current, loadCount).then(result => {
return {
count: result.count,
items: result.photos
};
});
if(peerId > 0) {
return appPhotosManager.getUserPhotos(peerId, (anchor || listLoader.current) as any, loadCount).then(result => {
return {
count: result.count,
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,
onJump: (item, older) => {
@ -292,7 +381,10 @@ class PeerProfileAvatars { @@ -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);
listLoader.load(true);
@ -310,11 +402,17 @@ class PeerProfileAvatars { @@ -310,11 +402,17 @@ class PeerProfileAvatars {
this.tabs.classList.toggle('hide', this.tabs.childElementCount <= 1);
}
public processItem = (photoId: string) => {
public processItem = (photoId: string | Message.messageService) => {
const avatar = document.createElement('div');
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();
img.classList.add(PeerProfileAvatars.BASE_CLASS + '-avatar-image');
img.draggable = false;
@ -357,6 +455,10 @@ class PeerProfile { @@ -357,6 +455,10 @@ class PeerProfile {
private peerId = 0;
private threadId: number;
constructor(public scrollable: Scrollable) {
}
public init() {
this.init = null;
@ -419,11 +521,17 @@ class PeerProfile { @@ -419,11 +521,17 @@ class PeerProfile {
});
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.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) => {
if(!e.isTrusted) {
@ -502,7 +610,7 @@ class PeerProfile { @@ -502,7 +610,7 @@ class PeerProfile {
if(photo) {
const oldAvatars = this.avatars;
this.avatars = new PeerProfileAvatars();
this.avatars = new PeerProfileAvatars(this.scrollable);
this.avatars.setPeer(this.peerId);
this.avatars.info.append(this.name, this.subtitle);
@ -511,10 +619,14 @@ class PeerProfile { @@ -511,10 +619,14 @@ class PeerProfile {
if(oldAvatars) oldAvatars.container.replaceWith(this.avatars.container);
else this.element.prepend(this.avatars.container);
this.scrollable.container.classList.add('parallax');
return;
}
}
this.scrollable.container.classList.remove('parallax');
if(this.avatars) {
this.avatars.container.remove();
this.avatars = undefined;
@ -699,7 +811,7 @@ export default class AppSharedMediaTab extends SliderSuperTab { @@ -699,7 +811,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
// * body
this.profile = new PeerProfile();
this.profile = new PeerProfile(this.scrollable);
this.profile.init();
this.scrollable.append(this.profile.element);
@ -712,6 +824,7 @@ export default class AppSharedMediaTab extends SliderSuperTab { @@ -712,6 +824,7 @@ export default class AppSharedMediaTab extends SliderSuperTab {
const top = rect.top;
const isSharedMedia = top <= HEADER_HEIGHT;
animatedCloseIcon.classList.toggle('state-back', isSharedMedia);
this.searchSuper.container.classList.toggle('is-full-viewport', isSharedMedia);
transition(+isSharedMedia);
if(!isSharedMedia) {

8
src/components/sortedUserList.ts

@ -1,8 +1,14 @@ @@ -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 { isInDOM, positionElementByIndex, replaceContent } from "../helpers/dom";
import { getHeavyAnimationPromise } from "../hooks/useHeavyAnimationCheck";
import appUsersManager from "../lib/appManagers/appUsersManager";
import { insertInDescendSortedArray, forEachReverse } from "../helpers/array";
import { insertInDescendSortedArray } from "../helpers/array";
type SortedUser = {
peerId: number,

4
src/components/swipeHandler.ts

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

4
src/components/wrappers.ts

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

2
src/config/app.ts

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

50
src/helpers/dom/getVisibleRect.ts

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

15
src/index.ts

@ -7,6 +7,7 @@ @@ -7,6 +7,7 @@
import App from './config/app';
import findUpClassName from './helpers/dom/findUpClassName';
import fixSafariStickyInput from './helpers/dom/fixSafariStickyInput';
import { isMobileSafari } from './helpers/userAgent';
import './materialize.scss';
import './scss/style.scss';
import './scss/tgico.scss';
@ -259,6 +260,20 @@ console.timeEnd('get storage1'); */ @@ -259,6 +260,20 @@ console.timeEnd('get storage1'); */
if(authState._ !== 'authStateSignedIn'/* || 1 === 1 */) {
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() => {
switch(authState._) {
case 'authStateSignIn':

16
src/lang.ts

@ -2,7 +2,7 @@ const lang = { @@ -2,7 +2,7 @@ const lang = {
"Animations": "Animations",
"AttachAlbum": "Album",
"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.",
"FilterNameInputLabel": "Folder Name",
"FilterMenuDelete": "Delete Folder",
@ -59,7 +59,6 @@ const lang = { @@ -59,7 +59,6 @@ const lang = {
"Checkbox.Disabled": "Disabled",
"Error.PreviewSender.CaptionTooLong": "Caption is too long.",
"PreviewSender.GroupItems": "Group items",
"PreviewSender.Send": "SEND",
"PreviewSender.SendAlbum": {
"one_value": "Send Album",
"other_value": "Send %d Albums"
@ -407,6 +406,9 @@ const lang = { @@ -407,6 +406,9 @@ const lang = {
"Chat.Date.ScheduledFor": "Scheduled for %@",
//"Chat.Date.ScheduledUntilOnline": "Scheduled until online",
"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.Channel.UpdatedTitle": "Channel renamed to \"%@\"",
"Chat.Service.Channel.UpdatedPhoto": "Channel photo updated",
@ -487,11 +489,21 @@ const lang = { @@ -487,11 +489,21 @@ const lang = {
"EditAccount.Username": "Username",
"EditAccount.Title": "Edit Profile",
"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": {
"one_value": "last seen %d hour ago",
"other_value": "last seen %d hours ago"
},
"Login.Register.LastName.Placeholder": "Last Name",
"Modal.Send": "Send",
"Telegram.GeneralSettingsViewController": "General Settings",
"Telegram.InstalledStickerPacksController": "Stickers",
"Telegram.NotificationSettingsViewController": "Notifications",

50
src/lib/appManagers/appImManager.ts

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

20
src/lib/appManagers/appMessagesManager.ts

@ -17,7 +17,7 @@ import { createPosterForVideo } from "../../helpers/files"; @@ -17,7 +17,7 @@ import { createPosterForVideo } from "../../helpers/files";
import { copy, defineNotNumerableProperties, getObjectKeysAndSort } from "../../helpers/object";
import { randomLong } from "../../helpers/random";
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 I18n, { i18n, join, langPack, LangPackKey, _i18n } from "../langPack";
import { logger, LogLevels } from "../logger";
@ -1548,6 +1548,24 @@ export class AppMessagesManager { @@ -1548,6 +1548,24 @@ export class AppMessagesManager {
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]) {
if(dialog) {
dialog.top_message = message.mid;

11
src/lib/appManagers/appPhotosManager.ts

@ -219,7 +219,7 @@ export class AppPhotosManager { @@ -219,7 +219,7 @@ export class AppPhotosManager {
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);
//console.log('setAttachmentSize', photo, photo.sizes[0].bytes, div);
@ -233,7 +233,12 @@ export class AppPhotosManager { @@ -233,7 +233,12 @@ export class AppPhotosManager {
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) {
element.setAttributeNS(null, 'width', '' + w);
element.setAttributeNS(null, 'height', '' + h);
@ -257,7 +262,7 @@ export class AppPhotosManager { @@ -257,7 +262,7 @@ export class AppPhotosManager {
}
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)) {
return appPhotosManager.getImageFromStrippedThumb(thumb as any);
}

8
src/lib/sessionStorage.ts

@ -4,9 +4,10 @@ @@ -4,9 +4,10 @@
* 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 { LangPackDifference } from '../layer';
import type { State } from './appManagers/appStateManager';
import AppStorage from './storage';
const sessionStorage = new AppStorage<{
@ -21,10 +22,7 @@ const sessionStorage = new AppStorage<{ @@ -21,10 +22,7 @@ const sessionStorage = new AppStorage<{
server_time_offset: number,
chatPositions: {
[peerId_threadId: string]: {
mid: number,
top: number
}
[peerId_threadId: string]: ChatSavedPosition
},
langPack: LangPackDifference
} & State>({

3
src/pages/pageSignIn.ts

@ -12,7 +12,6 @@ import apiManager from "../lib/mtproto/mtprotoworker"; @@ -12,7 +12,6 @@ import apiManager from "../lib/mtproto/mtprotoworker";
import { RichTextProcessor } from '../lib/richtextprocessor';
import { attachClickEvent, cancelEvent, replaceContent } from "../helpers/dom";
import Page from "./page";
import pageAuthCode from "./pageAuthCode";
import InputField from "../components/inputField";
import CheckboxField from "../components/checkboxField";
import Button from "../components/button";
@ -341,7 +340,7 @@ let onFirstMount = () => { @@ -341,7 +340,7 @@ let onFirstMount = () => {
}).then((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 => {
this.removeAttribute('disabled');

11
src/pages/pagesManager.ts

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

9
src/scss/partials/_avatar.scss

@ -7,8 +7,8 @@ @@ -7,8 +7,8 @@
avatar-element {
--size: 54px;
--multiplier: 1;
--color-top: var(--peer-avatar-blue-top);
--color-bottom: var(--peer-avatar-blue-bottom);
--color-top: var(--avatar-color-top);
--color-bottom: var(--avatar-color-bottom);
color: #fff;
width: var(--size);
height: var(--size);
@ -52,6 +52,11 @@ avatar-element { @@ -52,6 +52,11 @@ avatar-element {
--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 {
font-size: calc(32px / var(--multiplier));
}

43
src/scss/partials/_button.scss

@ -195,49 +195,6 @@ @@ -195,49 +195,6 @@
&-text {
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 {

6
src/scss/partials/_chat.scss

@ -833,7 +833,7 @@ $chat-helper-size: 39px; @@ -833,7 +833,7 @@ $chat-helper-size: 39px;
.btn-menu {
padding: 8px 0;
right: -11px;
bottom: calc(100% + 16px);
bottom: calc(100% + 1.25rem);
> div {
padding: 0 38px 0 16px;
@ -980,7 +980,7 @@ $chat-helper-size: 39px; @@ -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;
@include respond-to(handhelds) {
@ -1130,7 +1130,7 @@ $chat-helper-size: 39px; @@ -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
}

11
src/scss/partials/_chatBubble.scss

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

5
src/scss/partials/_chatTopbar.scss

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

4
src/scss/partials/_chatlist.scss

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

44
src/scss/partials/_checkbox.scss

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

47
src/scss/partials/_leftSidebar.scss

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

8
src/scss/partials/_mediaViewer.scss

@ -282,11 +282,15 @@ @@ -282,11 +282,15 @@
}
&.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 {
transition: var(--move-duration) transform ease;
transition: transform var(--move-duration) ease;
}
/* &.center {

36
src/scss/partials/_profile.scss

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

44
src/scss/partials/_rightSidebar.scss

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

2
src/scss/partials/_ripple.scss

@ -116,4 +116,4 @@ @@ -116,4 +116,4 @@
to {
transform: scale(2);
}
}
}

110
src/scss/partials/_row.scss

@ -0,0 +1,110 @@ @@ -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 @@ @@ -4,6 +4,23 @@
* 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 {
width: 100%;
display: flex;
@ -64,7 +81,7 @@ @@ -64,7 +81,7 @@
i {
position: absolute;
bottom: calc(-.625rem - 2px);
bottom: calc(-.625rem - 3px);
left: 0;
opacity: 0;
background-color: var(--primary-color);
@ -72,8 +89,8 @@ @@ -72,8 +89,8 @@
width: 100%;
border-radius: .1875rem .1875rem 0 0;
pointer-events: none;
padding-right: .5rem;
margin-left: -.25rem;
/* padding-right: .5rem;
margin-left: -.25rem; */
box-sizing: content-box;
transform-origin: left;
z-index: 1;

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

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

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

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

115
src/scss/style.scss

@ -118,7 +118,7 @@ $chat-padding-handhelds: .5rem; @@ -118,7 +118,7 @@ $chat-padding-handhelds: .5rem;
:root {
// * Day theme
--body-background-color: #fff;
//--background-color: #f4f4f5;
--background-color-true: #f4f4f5;
--background-color: #fff;
--border-color: #dfe1e5;
--surface-color: #fff;
@ -136,6 +136,8 @@ $chat-padding-handhelds: .5rem; @@ -136,6 +136,8 @@ $chat-padding-handhelds: .5rem;
@include splitColor(danger-color, #df3f40, true, false);
--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-pinned-color: #a2abb2;
--badge-text-color: #fff;
@ -159,7 +161,7 @@ html.night { @@ -159,7 +161,7 @@ html.night {
//:root {
// * Night theme
--body-background-color: #181818;
//--background-color: #181818;
--background-color-true: #181818;
--background-color: #212121;
--border-color: #0f0f0f;
--surface-color: #212121;
@ -177,6 +179,8 @@ html.night { @@ -177,6 +179,8 @@ html.night {
@include splitColor(danger-color, #ff595a, true, false);
--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-pinned-color: var(--secondary-color);
--badge-text-color: #fff;
@ -231,6 +235,7 @@ html.night { @@ -231,6 +235,7 @@ html.night {
@import "partials/peerTyping";
@import "partials/poll";
@import "partials/transition";
@import "partials/row";
@import "partials/popups/popup";
@import "partials/popups/editAvatar";
@ -1009,94 +1014,6 @@ middle-ellipsis-element { @@ -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 {
@include hover-background-effect();
}
@ -1193,3 +1110,21 @@ middle-ellipsis-element { @@ -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