Browse Source

Font changed to 16px; sign in page fixes; prepare auth animation

master
morethanwords 4 years ago
parent
commit
4c500ada12
  1. 6
      package-lock.json
  2. 3
      package.json
  3. 50
      src/components/emoticonsDropdown.ts
  4. 6
      src/components/pageSignIn.ts
  5. 6
      src/components/scrollable.ts
  6. 2
      src/components/wrappers.ts
  7. 2
      src/lib/appManagers/appImManager.ts
  8. 6
      src/lib/appManagers/appSidebarLeft.ts
  9. 18
      src/lib/appManagers/appStickersManager.ts
  10. 26
      src/lib/bin_utils.ts
  11. 2184
      src/lib/crypto/crypto.js
  12. 78
      src/lib/crypto/crypto_utils.ts
  13. 38
      src/lib/crypto/cryptoworker.ts
  14. 18
      src/lib/mtproto/apiManager.ts
  15. 13
      src/lib/mtproto/transports/abridged.ts
  16. 4
      src/lib/mtproto/transports/codec.ts
  17. 49
      src/lib/mtproto/transports/intermediate.ts
  18. 79
      src/lib/mtproto/transports/websocket.ts
  19. 7
      src/lib/polyfill.ts
  20. 35
      src/scss/partials/_chat.scss
  21. 28
      src/scss/partials/_chatlist.scss
  22. 48
      src/scss/partials/_emojiDropdown.scss
  23. 2
      src/scss/partials/_sidebar.scss
  24. 72
      src/scss/style.scss

6
package-lock.json generated

@ -1312,6 +1312,12 @@ @@ -1312,6 +1312,12 @@
"minimist": "^1.2.0"
}
},
"@cryptography/aes": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/@cryptography/aes/-/aes-0.1.1.tgz",
"integrity": "sha512-PcYz4FDGblO6tM2kSC+VzhhK62vml6k6/YAkiWtyPvrgJVfnDRoHGDtKn5UiaRRUrvUTTocBpvc2rRgTCqxjsg==",
"dev": true
},
"@cryptography/sha1": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@cryptography/sha1/-/sha1-0.1.0.tgz",

3
package.json

@ -6,7 +6,7 @@ @@ -6,7 +6,7 @@
"scripts": {
"start": "webpack-dev-server --open --config webpack.dev.js",
"start-production": "webpack-dev-server --open --config webpack.prod.js",
"serve-serve": "webpack-serve --compress --port 9001 --host localhost --no-watch --static ./public --config webpack.prod.js",
"serve": "node server.js",
"build": "webpack --config webpack.prod.js",
"test": "jest --config=jest.config.js",
"profile": "webpack --profile --json > stats.json --config webpack.prod.js"
@ -21,6 +21,7 @@ @@ -21,6 +21,7 @@
"@babel/core": "^7.9.0",
"@babel/preset-env": "^7.9.5",
"@babel/preset-typescript": "^7.9.0",
"@cryptography/aes": "^0.1.1",
"@cryptography/sha1": "^0.1.0",
"@cryptography/sha256": "^0.2.0",
"@types/aes-js": "^3.1.1",

50
src/components/emoticonsDropdown.ts

@ -71,9 +71,9 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, @@ -71,9 +71,9 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
if(menuScroll) {
if(i < heights.length - 4) {
menuScroll.container.scrollLeft = (i - 3) * 50;
menuScroll.container.scrollLeft = (i - 3) * 47;
} else {
menuScroll.container.scrollLeft = i * 50;
menuScroll.container.scrollLeft = i * 47;
}
}
@ -85,7 +85,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, @@ -85,7 +85,7 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
};
{
let categories = ["Smileys & Emotion", "Animals & Nature", "Food & Drink", "Travel & Places", "Activities", "Objects", "Symbols", "Flags", "Skin Tones"];
let categories = ["Smileys & Emotion", "Animals & Nature", "Food & Drink", "Travel & Places", "Activities", "Objects", /* "Symbols", */"Flags", "Skin Tones"];
let divs: {
[category: string]: HTMLDivElement
} = {};
@ -100,12 +100,16 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, @@ -100,12 +100,16 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
let details = Config.Emoji.emoji[unified];
let category = details[keyCategory];
if(category == 'Symbols') category = 'Objects';
details.unified = unified;
if(!sorted[category]) sorted[category] = [];
sorted[category][details.sort_order] = details;
}
//console.log('emoticons sorted:', sorted);
Object.keys(sorted).forEach(c => sorted[c].sort());
categories.pop();
@ -143,20 +147,29 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, @@ -143,20 +147,29 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
}
//console.timeEnd('emojiParse');
let contentEmojiDiv = document.getElementById('content-emoji') as HTMLDivElement;
let heights: number[] = [0];
let contentEmojiDiv = document.getElementById('content-emoji') as HTMLDivElement;
categories.forEach(category => {
let prevCategoryIndex = 1;
let menu = contentEmojiDiv.nextElementSibling as HTMLUListElement;
let emojiScroll = new Scrollable(contentEmojiDiv, 'y', 500, 'EMOJI', null);
emojiScroll.container.addEventListener('scroll', (e) => {
prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, emojiScroll.container);
});
//emojiScroll.setVirtualContainer(emojiScroll.container);
categories.map(category => {
let div = divs[category];
if(!div) {
console.error('no div by category:', category);
}
contentEmojiDiv.append(div);
emojiScroll.append(div);
return div;
}).forEach(div => {
//console.log('emoji heights push: ', (heights[heights.length - 1] || 0) + div.scrollHeight, div, div.scrollHeight);
heights.push((heights[heights.length - 1] || 0) + div.scrollHeight);
//console.log(div, div.scrollHeight);
});
contentEmojiDiv.addEventListener('click', function(e) {
@ -184,14 +197,6 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, @@ -184,14 +197,6 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
btnSend.classList.remove('tgico-microphone2');
});
let prevCategoryIndex = 1;
let menu = contentEmojiDiv.nextElementSibling as HTMLUListElement;
let emojiScroll = new Scrollable(contentEmojiDiv, 'y', 500, 'EMOJI', contentEmojiDiv);
emojiScroll.container.addEventListener('scroll', (e) => {
prevCategoryIndex = emoticonsContentOnScroll(menu, heights, prevCategoryIndex, emojiScroll.container);
});
//emojiScroll.setVirtualContainer(emojiScroll.container);
emoticonsMenuOnClick(menu, heights, emojiScroll);
}
@ -273,13 +278,15 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, @@ -273,13 +278,15 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
heightRAF = window.requestAnimationFrame(() => {
heightRAF = 0;
let paddingTop = parseInt(window.getComputedStyle(stickersScroll.container).getPropertyValue('padding-top')) || 0;
heights.length = 0;
let concated = stickersScroll.hiddenElements.up.concat(stickersScroll.visibleElements, stickersScroll.hiddenElements.down);
concated.forEach((el, i) => {
heights[i] = (heights[i - 1] || 0) + el.height;
heights[i] = (heights[i - 1] || 0) + el.height + (i == 0 ? paddingTop : 0);
});
console.log('stickers concated', concated, heights);
//console.log('stickers concated', concated, heights);
});
/* Array.from(stickersDiv.children).forEach((div, i) => {
@ -343,12 +350,13 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement, @@ -343,12 +350,13 @@ const initEmoticonsDropdown = (pageEl: HTMLDivElement,
//stickersScroll.append(categoryDiv);
let stickerSet = await appStickersManager.getStickerSet(set);
//console.log('got stickerSet', stickerSet, li);
if(stickerSet.set.thumb) {
let thumb = stickerSet.set.thumb;
appStickersManager.getStickerSetThumb(stickerSet.set).then((blob) => {
if(thumb.w == 1 && thumb.h == 1) { // means animated
//console.log('setting thumb', stickerSet, blob);
if(stickerSet.set.pFlags.animated) { // means animated
const reader = new FileReader();
reader.addEventListener('loadend', async(e) => {

6
src/components/pageSignIn.ts

@ -38,7 +38,7 @@ export default () => { @@ -38,7 +38,7 @@ export default () => {
installed = true;
//const countries: Country[] = _countries.default.filter(c => c.emoji);
const countries: Country[] = Config.Countries.filter(c => c.emoji);
const countries: Country[] = Config.Countries.filter(c => c.emoji).sort((a, b) => a.name.localeCompare(b.name));
let lastCountrySelected = '';
@ -174,9 +174,9 @@ export default () => { @@ -174,9 +174,9 @@ export default () => {
}
if(country && (this.value.length - 1) >= (country.pattern ? country.pattern.length : 9)) {
btnNext.style.display = '';
btnNext.style.visibility = '';
} else {
btnNext.style.display = 'none';
btnNext.style.visibility = 'hidden';
}
});

6
src/components/scrollable.ts

@ -104,6 +104,10 @@ export default class Scrollable { @@ -104,6 +104,10 @@ export default class Scrollable {
constructor(public el: HTMLElement, axis: 'y' | 'x' = 'y', public splitOffset = 300, logPrefix = '', public appendTo = el, public onScrollOffset = splitOffset) {
this.container = document.createElement('div');
this.container.classList.add('scrollable');
if(!appendTo) {
this.appendTo = this.container;
}
this.log = logger('SCROLL' + (logPrefix ? '-' + logPrefix : ''));
@ -413,7 +417,7 @@ export default class Scrollable { @@ -413,7 +417,7 @@ export default class Scrollable {
let appendTo = this.splitUp || this.appendTo;
clearTimeout(this.disableHoverTimeout);
if(this.el != this.appendTo) {
if(this.el != this.appendTo && this.appendTo != this.container) {
if(!appendTo.classList.contains('disable-hover')) {
appendTo.classList.add('disable-hover');
}

2
src/components/wrappers.ts

@ -543,7 +543,7 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: ( @@ -543,7 +543,7 @@ export function wrapSticker(doc: MTDocument, div: HTMLDivElement, middleware?: (
if(doc.thumbs && !div.firstElementChild && (!doc.downloaded || stickerType == 2)) {
let thumb = doc.thumbs[0];
console.log('wrap sticker', thumb, div);
//console.log('wrap sticker', thumb, div);
if(thumb.bytes) {
apiFileManager.saveSmallFile(thumb.location, thumb.bytes);

2
src/lib/appManagers/appImManager.ts

@ -1336,7 +1336,7 @@ export class AppImManager { @@ -1336,7 +1336,7 @@ export class AppImManager {
this.log('messageMediaDocument', doc, bubble);
if(doc.sticker && doc.size <= 1e6) {
if(doc.sticker/* && doc.size <= 1e6 */) {
bubble.classList.add('sticker');
if(doc.animated) {

6
src/lib/appManagers/appSidebarLeft.ts

@ -8,6 +8,7 @@ import appMessagesIDsManager from "./appMessagesIDsManager"; @@ -8,6 +8,7 @@ import appMessagesIDsManager from "./appMessagesIDsManager";
import appImManager from "./appImManager";
import appUsersManager from "./appUsersManager";
import { appPeersManager } from "../services";
import apiManager from "../mtproto/apiManager";
let testScroll = false;
@ -50,6 +51,7 @@ class AppSidebarLeft { @@ -50,6 +51,7 @@ class AppSidebarLeft {
private menuEl = this.toolsBtn.querySelector('.btn-menu');
private savedBtn = this.menuEl.querySelector('.menu-saved');
private archivedBtn = this.menuEl.querySelector('.menu-archive');
private logOutBtn = this.menuEl.querySelector('.menu-logout');
public archivedCount = this.archivedBtn.querySelector('.archived-count') as HTMLSpanElement;
private listsContainer: HTMLDivElement = null;
@ -130,6 +132,10 @@ class AppSidebarLeft { @@ -130,6 +132,10 @@ class AppSidebarLeft {
//this.toolsBtn.classList.remove('tgico-menu', 'btn-menu-toggle');
//this.toolsBtn.classList.add('tgico-back');
});
this.logOutBtn.addEventListener('click', (e) => {
apiManager.logOut();
});
if(testScroll) {
for(let i = 0; i < 1000; ++i) {

18
src/lib/appManagers/appStickersManager.ts

@ -2,6 +2,7 @@ import AppStorage from '../storage'; @@ -2,6 +2,7 @@ import AppStorage from '../storage';
import { MTDocument } from '../../components/wrappers';
import apiManager from '../mtproto/apiManager';
import apiFileManager from '../mtproto/apiFileManager';
import appDocsManager from './appDocsManager';
export type MTStickerSet = {
_: 'stickerSet',
@ -27,6 +28,9 @@ export type MTStickerSet = { @@ -27,6 +28,9 @@ export type MTStickerSet = {
h: number,
size: number
},
pFlags: {
animated?: boolean
}
thumb_dc_id?: number,
count: number,
hash: number
@ -52,12 +56,23 @@ class appStickersManager { @@ -52,12 +56,23 @@ class appStickersManager {
[stickerSetID: string]: MTStickerSetFull
}>('stickerSets').then((sets) => {
if(sets) {
for(let id in sets) {
let set = sets[id];
set.documents.forEach(doc => {
delete doc.downloaded;
delete doc.url;
this.saveSticker(doc);
});
}
this.stickerSets = sets;
}
});
}
public saveSticker(doc: MTDocument) {
if(this.documents[doc.id]) return this.documents[doc.id];
/* Object.keys(doc).forEach(key => {
if(doc[key] instanceof Uint8Array) {
doc[key] = Array.from(doc[key]);
@ -66,7 +81,10 @@ class appStickersManager { @@ -66,7 +81,10 @@ class appStickersManager {
doc.file_reference = Array.from(doc.file_reference);
appDocsManager.saveDoc(doc);
this.documents[doc.id] = doc;
return doc;
}
public getSticker(fileID: string) {

26
src/lib/bin_utils.ts

@ -282,7 +282,7 @@ export function longToInts(sLong: string) { @@ -282,7 +282,7 @@ export function longToInts(sLong: string) {
return [divRem[0].intValue(), divRem[1].intValue()];
}
export function bytesFromWords(wordArray: any) {
export function bytesFromWords(wordArray: {words: number[] | Uint8Array | Uint32Array, sigBytes: number}) {
var words = wordArray.words;
var sigBytes = wordArray.sigBytes;
var bytes = [];
@ -294,6 +294,30 @@ export function bytesFromWords(wordArray: any) { @@ -294,6 +294,30 @@ export function bytesFromWords(wordArray: any) {
return bytes;
}
export function bytesFromWordss(input: Uint32Array) {
var o = [];
for(var i = 0; i < input.length * 4; i++) {
o.push((input[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff);
}
return o;
}
export function bytesToWordss(input: ArrayBuffer | Uint8Array) {
let bytes: Uint8Array;
if(input instanceof ArrayBuffer) bytes = new Uint8Array(input);
else bytes = input;
var len = bytes.length;
var words: number[] = [];
var i;
for(i = 0; i < len; i++) {
words[i >>> 2] |= bytes[i] << (24 - (i % 4) * 8);
}
return new Uint32Array(words);
}
export function longToBytes(sLong: string) {
return bytesFromWords({words: longToInts(sLong), sigBytes: 8}).reverse();
}

2184
src/lib/crypto/crypto.js

File diff suppressed because it is too large Load Diff

78
src/lib/crypto/crypto_utils.ts

@ -1,5 +1,6 @@ @@ -1,5 +1,6 @@
import sha1 from '@cryptography/sha1';
import sha256 from '@cryptography/sha256';
import {IGE} from '@cryptography/aes';
import {str2bigInt, bpe, equalsInt, greater,
copy_, eGCD_, add_, rightShift_, sub_, copyInt_, isZero,
@ -9,43 +10,13 @@ import {str2bigInt, bpe, equalsInt, greater, @@ -9,43 +10,13 @@ import {str2bigInt, bpe, equalsInt, greater,
// @ts-ignore
import {BigInteger} from 'jsbn';
import CryptoJS from './crypto.js';
import { addPadding, bytesToHex, bytesFromHex, nextRandomInt, bytesFromBigInt, dT, bytesFromWords } from '../bin_utils';
import { addPadding, bytesToHex, bytesFromHex, nextRandomInt, bytesFromBigInt, dT, bytesFromWords, bytesToWordss, bytesFromWordss } from '../bin_utils';
export function bytesFromLeemonBigInt(bigInt: BigInteger) {
var str = bigInt2str(bigInt, 16);
return bytesFromHex(str);
}
export function bytesToWordss(input: ArrayBuffer | Uint8Array) {
let bytes: Uint8Array;
if(input instanceof ArrayBuffer) bytes = new Uint8Array(input);
else bytes = input;
var len = bytes.length;
var words: number[] = [];
var i;
for(i = 0; i < len; i++) {
words[i >>> 2] |= bytes[i] << (24 - (i % 4) * 8);
}
return new Uint32Array(words);
}
export function bytesToWords(bytes: any) {
if(bytes instanceof ArrayBuffer) {
bytes = new Uint8Array(bytes);
}
var len = bytes.length;
var words: any = [];
var i;
for(i = 0; i < len; i++) {
words[i >>> 2] |= bytes[i] << (24 - (i % 4) * 8);
}
return new CryptoJS.lib.WordArray.init(words, len);
}
export function sha1HashSync(bytes: number[] | ArrayBuffer | Uint8Array) {
//console.trace(dT(), 'SHA-1 hash start', bytes);
@ -68,54 +39,35 @@ export function sha256HashSync(bytes: Uint8Array | ArrayBuffer) { @@ -68,54 +39,35 @@ export function sha256HashSync(bytes: Uint8Array | ArrayBuffer) {
let words = bytesToWordss(bytes);
let hash = sha256(words);
// bytesFromWords below
var o = [];
for(var i = 0; i < hash.length * 4; i++) {
o.push((hash[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff);
}
//console.log(dT(), 'SHA-256 hash finish');
return o;
return bytesFromWordss(hash);
}
export function aesEncryptSync(bytes: any, keyBytes: any, ivBytes: any) {
// console.log(dT(), 'AES encrypt start', len/*, bytesToHex(keyBytes), bytesToHex(ivBytes)*/)
export function aesEncryptSync(bytes: ArrayBuffer, keyBytes: ArrayBuffer, ivBytes: ArrayBuffer) {
//console.log(dT(), 'AES encrypt start', bytes, keyBytes, ivBytes);
// console.log('aes before padding bytes:', bytesToHex(bytes));
bytes = addPadding(bytes);
// console.log('aes after padding bytes:', bytesToHex(bytes));
let mode = CryptoJS.mode.IGE;
const cipher = new IGE(bytesToWordss(keyBytes), bytesToWordss(ivBytes));
const encryptedBytes = cipher.encrypt(bytesToWordss(bytes));
//console.log(dT(), 'AES encrypt finish');
let encryptedWords = CryptoJS.AES.encrypt(bytesToWords(bytes), bytesToWords(keyBytes), {
iv: bytesToWords(ivBytes),
padding: CryptoJS.pad.NoPadding,
mode//: CryptoJS.mode.IGE
}).ciphertext;
let encryptedBytes = bytesFromWords(encryptedWords);
// console.log(dT(), 'AES encrypt finish')
return encryptedBytes;
return bytesFromWordss(encryptedBytes);
}
export function aesDecryptSync(encryptedBytes: any, keyBytes: any, ivBytes: any) {
export function aesDecryptSync(bytes: Uint8Array, keyBytes: Uint8Array, ivBytes: Uint8Array) {
//console.log(dT(), 'AES decrypt start', bytes, keyBytes, ivBytes);
let mode = CryptoJS.mode.IGE;
// console.log(dT(), 'AES decrypt start', encryptedBytes.length)
var decryptedWords = CryptoJS.AES.decrypt({ciphertext: bytesToWords(encryptedBytes)}, bytesToWords(keyBytes), {
iv: bytesToWords(ivBytes),
padding: CryptoJS.pad.NoPadding,
mode//: CryptoJS.mode.IGE
});
const cipher = new IGE(bytesToWordss(keyBytes), bytesToWordss(ivBytes));
const decryptedBytes = cipher.decrypt(bytesToWordss(bytes));
var bytes = bytesFromWords(decryptedWords);
// console.log(dT(), 'AES decrypt finish')
//console.log(dT(), 'AES decrypt finish');
return bytes;
return bytesFromWordss(decryptedBytes);
}
export function rsaEncrypt(publicKey: {modulus: string, exponent: string}, bytes: any): number[] {
console.log(dT(), 'RSA encrypt start', publicKey, bytes);

38
src/lib/crypto/cryptoworker.ts

@ -88,36 +88,26 @@ class CryptoWorker { @@ -88,36 +88,26 @@ class CryptoWorker {
}
public sha1Hash(bytes: number[] | ArrayBuffer | Uint8Array): Promise<Uint8Array> {
//if(this.webWorker) {
return this.performTaskWorker<Uint8Array>('sha1-hash', bytes);
//}
return this.performTaskWorker<Uint8Array>('sha1-hash', bytes);
}
public sha256Hash(bytes: any) {
//if(this.webWorker) {
return this.performTaskWorker<number[]>('sha256-hash', bytes);
//}
return this.performTaskWorker<number[]>('sha256-hash', bytes);
}
public pbkdf2(buffer: Uint8Array, salt: Uint8Array, iterations: number) {
//if(this.webWorker) {
return this.performTaskWorker<ArrayBuffer>('pbkdf2', buffer, salt, iterations);
//}
return this.performTaskWorker<ArrayBuffer>('pbkdf2', buffer, salt, iterations);
}
public aesEncrypt(bytes: any, keyBytes: any, ivBytes: any) {
//if(this.webWorker) {
return this.performTaskWorker<ArrayBuffer>('aes-encrypt', convertToArrayBuffer(bytes),
convertToArrayBuffer(keyBytes), convertToArrayBuffer(ivBytes));
//}
return this.performTaskWorker<ArrayBuffer>('aes-encrypt', convertToArrayBuffer(bytes),
convertToArrayBuffer(keyBytes), convertToArrayBuffer(ivBytes));
}
public aesDecrypt(encryptedBytes: any, keyBytes: any, ivBytes: any): Promise<ArrayBuffer> {
//if(this.webWorker) {
return this.performTaskWorker<ArrayBuffer>('aes-decrypt',
encryptedBytes, keyBytes, ivBytes)
.then(bytes => convertToArrayBuffer(bytes));
//}
return this.performTaskWorker<ArrayBuffer>('aes-decrypt',
encryptedBytes, keyBytes, ivBytes)
.then(bytes => convertToArrayBuffer(bytes));
}
public rsaEncrypt(publicKey: {modulus: string, exponent: string}, bytes: any): Promise<number[]> {
@ -127,21 +117,15 @@ class CryptoWorker { @@ -127,21 +117,15 @@ class CryptoWorker {
public factorize(bytes: any) {
bytes = convertToByteArray(bytes);
//if(this.webWorker) {
return this.performTaskWorker<[number[], number[], number]>('factorize', bytes);
//}
return this.performTaskWorker<[number[], number[], number]>('factorize', bytes);
}
public modPow(x: any, y: any, m: any) {
//if(this.webWorker) {
return this.performTaskWorker<number[]>('mod-pow', x, y, m);
//}
return this.performTaskWorker<number[]>('mod-pow', x, y, m);
}
public gzipUncompress<T>(bytes: ArrayBuffer, toString?: boolean) {
//if(this.webWorker) {
return this.performTaskWorker<T>('unzip', bytes, toString);
//}
return this.performTaskWorker<T>('unzip', bytes, toString);
}
}

18
src/lib/mtproto/apiManager.ts

@ -78,14 +78,16 @@ export class ApiManager { @@ -78,14 +78,16 @@ export class ApiManager {
AppStorage.remove('dc', 'user_auth', 'stickerSets');
this.baseDcID = 0;
this.telegramMeNotify(false);
return this.mtpClearStorage();
this.mtpClearStorage();
}, (error) => {
storageKeys.push('dc', 'user_auth', 'stickerSets');
AppStorage.remove(storageKeys);
this.baseDcID = 0;
error.handled = true;
this.telegramMeNotify(false);
return this.mtpClearStorage();
this.mtpClearStorage();
}).then(() => {
location.pathname = '/';
});
}
@ -224,17 +226,7 @@ export class ApiManager { @@ -224,17 +226,7 @@ export class ApiManager {
setTimeout(() => {
if(!error.handled) {
if(error.code == 401) {
// @ts-ignore WARNING!
this.logOut().finally(() => {
if(location.protocol == 'http:' &&
!Config.Modes.http &&
Config.App.domains.indexOf(location.hostname) != -1) {
location.href = location.href.replace(/^http:/, 'https:');
} else {
location.hash = '/login';
// AppRuntimeManager.reload(); // WARNING
}
})
this.logOut();
} else {
// ErrorService.show({error: error}); // WARNING
}

13
src/lib/mtproto/transports/abridged.ts

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
import { bytesFromHex, addPadding } from "../../bin_utils";
//import { bytesFromHex, addPadding } from "../../bin_utils";
import { Codec } from "./codec";
class AbridgedPacketCodec {
class AbridgedPacketCodec implements Codec {
public tag = 0xef;
public obfuscateTag = new Uint8Array([this.tag, this.tag, this.tag, this.tag]);
@ -9,12 +10,14 @@ class AbridgedPacketCodec { @@ -9,12 +10,14 @@ class AbridgedPacketCodec {
let header: Uint8Array;
if(len < 127) {
header = new Uint8Array([len]);
} else {
header = new Uint8Array([0x7f, ...addPadding(bytesFromHex(len.toString(16)).reverse(), 3, true)/* .reverse() */]);
} else { // Length: payload length, divided by four, and encoded as 3 length bytes (little endian)
//header = new Uint8Array([0x7f, ...addPadding(bytesFromHex(len.toString(16)).reverse(), 3, true)/* .reverse() */]);
header = new Uint8Array([0x7f, len & 0xFF, (len >> 8) & 0xFF, (len >> 16) & 0xFF]);
//console.log('got nobody cause im braindead', header, len);
}
return new Uint8Array([...header, ...data]);
return header.concat(data);
//return new Uint8Array([...header, ...data]);
}
public readPacket(data: Uint8Array) {

4
src/lib/mtproto/transports/codec.ts

@ -1,6 +1,6 @@ @@ -1,6 +1,6 @@
export interface Codec {
tag?: number;
obfuscateTag?: Uint8Array;
tag: number;
obfuscateTag: Uint8Array;
encodePacket: (data: Uint8Array) => Uint8Array;
readPacket: (data: Uint8Array) => Uint8Array;

49
src/lib/mtproto/transports/intermediate.ts

@ -0,0 +1,49 @@ @@ -0,0 +1,49 @@
import { nextRandomInt } from "../../bin_utils";
import { Codec } from "./codec";
class IntermediatePacketCodec implements Codec {
public tag = 0xee;
public obfuscateTag = new Uint8Array([this.tag, this.tag, this.tag, this.tag]);
public encodePacket(data: Uint8Array) {
let len = data.byteLength;
let header = new Uint8Array(new Uint32Array([len]).buffer);
return header.concat(data);
}
public readPacket(data: Uint8Array) {
let length = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24);
return data.slice(4, 4 + length);
}
}
/* Data packets are aligned to 4bytes. This codec adds random bytes of size
from 0 to 3 bytes, which are ignored by decoder. */
class PaddedIntermediatePacketCodec extends IntermediatePacketCodec {
public tag = 0xdd;
public obfuscateTag = new Uint8Array([this.tag, this.tag, this.tag, this.tag]);
public encodePacket(data: Uint8Array) {
let padding = new Uint8Array(nextRandomInt(3)).randomize();
let len = data.byteLength + padding.byteLength;
let header = new Uint8Array(new Uint32Array([len]).buffer);
console.log('encodePacket', padding, len, data, header);
return header.concat(data, padding);
}
public readPacket(data: Uint8Array) {
let padLength = data.byteLength % 4;
if(padLength > 0) {
return data.slice(4, -padLength);
}
return data.slice(4);
}
}
export default new IntermediatePacketCodec();
//export default new PaddedIntermediatePacketCodec();

79
src/lib/mtproto/transports/websocket.ts

@ -1,15 +1,25 @@ @@ -1,15 +1,25 @@
import MTTransport from './transport';
import aesjs from 'aes-js';
import abridgetPacketCodec from './abridged';
//import aesjs from 'aes-js';
import {CTR} from '@cryptography/aes';
//import abridgetPacketCodec from './abridged';
import intermediatePacketCodec from './intermediate';
import {MTPNetworker} from '../networker';
import { logger } from '../../polyfill';
import { bytesFromWordss } from '../../bin_utils';
import { Codec } from './codec';
/*
@cryptography/aes не работает с массивами которые не кратны 4, поэтому использую intermediate а не abridged
*/
export class Obfuscation {
public enc: aesjs.ModeOfOperation.ModeOfOperationCTR;
public dec: aesjs.ModeOfOperation.ModeOfOperationCTR;
/* public enc: aesjs.ModeOfOperation.ModeOfOperationCTR;
public dec: aesjs.ModeOfOperation.ModeOfOperationCTR; */
public init() {
public encNew: CTR;
public decNew: CTR;
public init(codec: Codec) {
const initPayload = new Uint8Array(64);
initPayload.randomize();
@ -22,6 +32,7 @@ export class Obfuscation { @@ -22,6 +32,7 @@ export class Obfuscation {
val != 0x20544547 &&
val != 0x4954504f &&
val != 0xeeeeeeee &&
val != 0xdddddddd &&
val2 != 0x00000000) {
//initPayload[56] = initPayload[57] = initPayload[58] = initPayload[59] = transport;
break;
@ -38,10 +49,13 @@ export class Obfuscation { @@ -38,10 +49,13 @@ export class Obfuscation {
let decKey = reversedPayload.slice(8, 40);
let decIv = reversedPayload.slice(40, 56);
this.enc = new aesjs.ModeOfOperation.ctr(encKey, new aesjs.Counter(encIv as any));
this.dec = new aesjs.ModeOfOperation.ctr(decKey, new aesjs.Counter(decIv as any));
/* this.enc = new aesjs.ModeOfOperation.ctr(encKey, new aesjs.Counter(encIv as any));
this.dec = new aesjs.ModeOfOperation.ctr(decKey, new aesjs.Counter(decIv as any)); */
this.encNew = new CTR(encKey, encIv);
this.decNew = new CTR(decKey, decIv);
initPayload.set(abridgetPacketCodec.obfuscateTag, 56);
initPayload.set(intermediatePacketCodec.obfuscateTag, 56);
const encrypted = this.encode(initPayload);
initPayload.set(encrypted.slice(56, 64), 56);
@ -49,12 +63,47 @@ export class Obfuscation { @@ -49,12 +63,47 @@ export class Obfuscation {
return initPayload;
}
/* public encode(payload: Uint8Array) {
let res = this.enc.encrypt(payload);
try {
let arr = this.encNew.encrypt(payload);
//let resNew = bytesFromWords({words: arr, sigBytes: arr.length});
let resNew = new Uint8Array(bytesFromWordss(arr));
console.log('Obfuscation: encode comparison:', res, arr, resNew, res.hex == resNew.hex);
} catch(err) {
console.error('Obfuscation: error:', err);
}
return res;
}
public decode(payload: Uint8Array) {
let res = this.dec.encrypt(payload);
try {
let arr = this.decNew.decrypt(payload);
//let resNew = bytesFromWords({words: arr, sigBytes: arr.length});
let resNew = new Uint8Array(bytesFromWordss(arr));
console.log('Obfuscation: decode comparison:', res, arr, resNew, res.hex == resNew.hex);
} catch(err) {
console.error('Obfuscation: error:', err);
}
return res;
} */
public encode(payload: Uint8Array) {
return this.enc.encrypt(payload);
let res = this.encNew.encrypt(payload);
let bytes = new Uint8Array(bytesFromWordss(res));
return bytes;
}
public decode(data: Uint8Array) {
return this.dec.encrypt(data);
public decode(payload: Uint8Array) {
let res = this.decNew.decrypt(payload);
let bytes = new Uint8Array(bytesFromWordss(res));
return bytes;
}
}
@ -75,6 +124,8 @@ export default class Socket extends MTTransport { @@ -75,6 +124,8 @@ export default class Socket extends MTTransport {
debug = false;
codec = intermediatePacketCodec;
constructor(dcID: number, url: string) {
super(dcID, url);
@ -103,7 +154,7 @@ export default class Socket extends MTTransport { @@ -103,7 +154,7 @@ export default class Socket extends MTTransport {
handleOpen = () => {
this.log('opened');
this.ws.send(this.obfuscation.init());
this.ws.send(this.obfuscation.init(this.codec));
this.connected = true;
this.releasePending();
@ -126,7 +177,7 @@ export default class Socket extends MTTransport { @@ -126,7 +177,7 @@ export default class Socket extends MTTransport {
this.debug && this.log('<-', 'handleMessage', event);
let data = this.obfuscation.decode(new Uint8Array(event.data));
data = abridgetPacketCodec.readPacket(data);
data = this.codec.readPacket(data);
if(this.networker) { // authenticated!
//this.pending = this.pending.filter(p => p.body); // clear pending
@ -175,7 +226,7 @@ export default class Socket extends MTTransport { @@ -175,7 +226,7 @@ export default class Socket extends MTTransport {
let pending = this.pending[i];
let {body} = pending;
if(body) {
let toEncode = abridgetPacketCodec.encodePacket(body);
let toEncode = this.codec.encodePacket(body);
//console.log('send before obf:', /* body.hex, nonce.hex, */ toEncode.hex);
let enc = this.obfuscation.encode(toEncode);

7
src/lib/polyfill.ts

@ -80,6 +80,10 @@ Uint8Array.prototype.concat = function(...args: Array<Uint8Array | ArrayBuffer | @@ -80,6 +80,10 @@ Uint8Array.prototype.concat = function(...args: Array<Uint8Array | ArrayBuffer |
return bufferConcats(this, ...args);
};
Uint8Array.prototype.toString = function() {
return String.fromCharCode.apply(null, [...this]);
};
Array.prototype.forEachReverse = function<T>(callback: (value: T, index?: number, array?: Array<T>) => void) {
let length = this.length;
for(var i = length - 1; i >= 0; --i) {
@ -108,7 +112,8 @@ declare global { @@ -108,7 +112,8 @@ declare global {
interface Uint8Array {
hex: string;
randomize: () => Uint8Array,
concat: (...args: Array<Uint8Array | ArrayBuffer | number[]>) => Uint8Array
concat: (...args: Array<Uint8Array | ArrayBuffer | number[]>) => Uint8Array,
toString: () => string
}
interface Array<T> {

35
src/scss/partials/_chat.scss

@ -1,3 +1,5 @@ @@ -1,3 +1,5 @@
$chat-max-width: 696px;
.chat-container {
display: flex;
// padding: 200px;
@ -57,7 +59,7 @@ @@ -57,7 +59,7 @@
display: flex;
align-items: center;
box-shadow: 0 1px 2px 0 rgba(16, 35, 47, 0.07);
padding: .5rem 1rem;
padding: .5rem 17px;
flex: 0 0 auto; /* Forces side columns to stay same width */
min-height: 60px;
max-height: 60px;
@ -71,8 +73,9 @@ @@ -71,8 +73,9 @@
}
.content {
padding-left: 1rem;
flex: 1;
padding-left: 17px;
line-height: 1.6;
}
.person {
@ -93,7 +96,7 @@ @@ -93,7 +96,7 @@
.bottom {
font-size: 14px;
line-height: 18px;
color: $placeholder-color;
color: #707579;
.online {
color: $darkblue;
@ -220,7 +223,7 @@ @@ -220,7 +223,7 @@
.bubble {
padding-top: 5px;
display: grid;
grid-template-columns: 1fr 700px 1fr;
grid-template-columns: 1fr $chat-max-width 1fr;
grid-row-gap: 0px;
&:before, &:after {
@ -1069,9 +1072,9 @@ @@ -1069,9 +1072,9 @@
display: flex;
align-items: center;
width: 100%;
max-width: 700px;
max-width: $chat-max-width;
padding-top: .35rem;
padding-bottom: 1rem;
padding-bottom: 21px;
justify-content: space-between;
flex: 0 0 auto; /* Forces side columns to stay same width */
position: relative;
@ -1080,8 +1083,8 @@ @@ -1080,8 +1083,8 @@
background: none;
border: none;
width: 100%;
padding: .5rem .5rem;
font-size: .95rem;
font-size: 16px;
padding: 10px 9px;
/* height: 100%; */
max-height: 30rem;
overflow-y: none;
@ -1093,7 +1096,7 @@ @@ -1093,7 +1096,7 @@
[contenteditable=true]:empty:before {
content: attr(data-placeholder);
color: #9e9e9e;
color: #a2acb4;
display: block; /* For Firefox By Ariel Flesler */
}
@ -1101,8 +1104,8 @@ @@ -1101,8 +1104,8 @@
flex: 0 0 auto;
font-size: 1.5rem;
line-height: 1.5rem;
height: 3.25rem;
width: 3.25rem;
height: 54px;
width: 54px;
color: #9e9e9e;
background-color: #fff;
align-self: flex-end;
@ -1130,7 +1133,7 @@ @@ -1130,7 +1133,7 @@
margin-right: 9px;
padding: 4.5px .5rem;
/* padding: 3px .5rem 6px .5rem; */
min-height: 3.25rem;
min-height: 54px;
max-height: 30rem;
caret-color: $button-primary-background;
flex: 1;
@ -1204,11 +1207,13 @@ @@ -1204,11 +1207,13 @@
.btn-icon {
display: block;
color: $placeholder-color;
font-size: 1.5rem;
line-height: 1.5rem;
transition: .2s color;
flex: 0 0 auto;
font-size: 24px;
line-height: 24px;
padding: 10px 7px 9px 7.5px;
color: #8d969c;
&.active {
color: $blue;

28
src/scss/partials/_chatlist.scss

@ -2,18 +2,19 @@ @@ -2,18 +2,19 @@
.input-search {
position: relative;
width: 100%;
margin-left: 1rem;
margin-left: 22px;
input {
background-color: rgba(112, 117, 121, .08);
border: 2px solid transparent;
height: 3rem;
height: 44px;
border-radius: 22px;
box-sizing: border-box;
padding: 0 1.5rem 0 3rem;
padding: 0px 1.5rem 0 40px;
-webkit-transition: all .15s ease-out;
transition: all .15s ease-out;
width: 100%;
font-size: 16px;
&:focus {
background-color: rgba(112, 117, 121, 0);
@ -28,15 +29,19 @@ @@ -28,15 +29,19 @@
.tgico {
position: absolute;
left: 1rem;
left: 12px;
top: 50%;
transform: translateY(-50%);
text-align: center;
font-size: 1.65rem;
font-size: 24px;
color: $color-gray;
opacity: .6;
-webkit-transition: all .15s ease-out;
transition: all .15s ease-out;
&:before {
vertical-align: middle;
}
}
}
@ -69,8 +74,8 @@ @@ -69,8 +74,8 @@
flex-direction: row;
position: relative;
cursor: pointer;
padding: 8px 8.5px;
margin: 0 8.5px 0 8px;
padding: 7px 8.5px;
margin: 0px 8px 2px 7px;
overflow: hidden;
&:hover {
@ -84,7 +89,7 @@ @@ -84,7 +89,7 @@
.pinned-delimiter {
display: flex;
padding: 8px 0 4px;
padding: 6px 0 6px;
span {
margin: 0;
@ -111,7 +116,9 @@ @@ -111,7 +116,9 @@
/* span:not(.tgico-pinnedchat):not(.emoji):last-child { */
.user-title + span {
/* font-size: .9rem; */
font-size: .8rem;
//font-size: .8rem;
font-size: .75rem;
padding: 2px 0px 0px 0px;
}
.user-last-message + span:not(.tgico-pinnedchat) {
@ -143,7 +150,7 @@ @@ -143,7 +150,7 @@
overflow: hidden;
color: $color-gray;
flex: 1 1 auto;
padding-right: 6.5px;
padding-right: 3.5px;
padding-left: 10px;
}
@ -209,6 +216,7 @@ @@ -209,6 +216,7 @@
color: #fff;
border-radius: 12px;
margin-top: 1.5px;
margin-right: -2px;
}
.unread, .unread-muted {

48
src/scss/partials/_emojiDropdown.scss

@ -8,7 +8,7 @@ @@ -8,7 +8,7 @@
height: 420px;
background: #fff;
box-shadow: 0px 5px 10px 5px rgba(16, 35, 47, 0.14);
border-radius: 12px;
border-radius: 10px;
z-index: 2;
/* display: none; */
display: flex;
@ -25,7 +25,14 @@ @@ -25,7 +25,14 @@
}
> .menu-horizontal {
padding: 0 3rem;
padding: 0px 58px 0px 58px;
font-weight: 500;
margin-top: 2px;
> li.active:after {
left: 29px;
right: 28px;
}
}
.emoji-container {
@ -34,6 +41,10 @@ @@ -34,6 +41,10 @@
overflow: hidden;
height: 100%;
}
.btn-icon {
color: #8d969c;
}
.tabs-container {
/* width: 300%; */
@ -42,19 +53,27 @@ @@ -42,19 +53,27 @@
.emoji-category {
font-size: 2.25rem;
line-height: 2.25rem;
margin-top: 1px;
padding-top: 1px;
display: grid;
grid-column-gap: 2.44px;
grid-template-columns: 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr;
&:first-child {
padding-top: 5px;
}
> * {
margin: 0;
padding: 5px 5px;
padding: 4px 4px;
line-height: inherit;
border-radius: 8px;
cursor: pointer;
user-select: none;
-webkit-user-select: none;
width: 44px;
height: 44px;
width: 42px;
height: 42px;
.emoji {
width: 100%;
@ -72,7 +91,7 @@ @@ -72,7 +91,7 @@
} */
}
.emoji-category, .sticker-category {
.sticker-category {
display: flex;
align-items: center;
justify-content: space-between;
@ -104,6 +123,7 @@ @@ -104,6 +123,7 @@
padding: 1px 2.5px;
justify-content: center;
border-radius: 12px;
padding: 0;
&:hover {
background-color: rgba(112, 117, 121, 0.08);
@ -129,7 +149,7 @@ @@ -129,7 +149,7 @@
> div:first-of-type {
flex: 1;
padding: 0;
padding-top: 10px;
//padding-top: 10px;
}
}
@ -146,9 +166,9 @@ @@ -146,9 +166,9 @@
.emoji-padding, .stickers-padding {
.menu-horizontal {
height: 50px;
height: 47px;
border-bottom: none;
padding: 0;
padding: 2px 2px 2px 2px;
width: 100%;
li {
@ -168,9 +188,11 @@ @@ -168,9 +188,11 @@
}
}
/* #content-stickers {
padding: 0;
} */
#content-stickers {
.scrollable {
padding: 15px 5px 0;
}
}
.emoji-padding, .stickers-padding {
.menu-horizontal > li {

2
src/scss/partials/_sidebar.scss

@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
display: flex;
align-items: center;
justify-content: space-between;
padding: .5rem 1.25rem;
padding: 7.5px 20px 7.5px 15px;
min-height: 60px;
.sidebar-title {

72
src/scss/style.scss

@ -48,7 +48,7 @@ html, body { @@ -48,7 +48,7 @@ html, body {
}
html {
font-size: 15px;
font-size: $text-size;
}
a {
@ -83,8 +83,8 @@ h1, h2, h3, h4, h5, h6 { @@ -83,8 +83,8 @@ h1, h2, h3, h4, h5, h6 {
}
h4 {
font-size: 2.28rem;
margin: 1.52rem 0 .912rem 0;
font-size: 2rem;
margin: 1.5rem 0 1rem 0;
line-height: 110%;
}
@ -94,7 +94,6 @@ input { @@ -94,7 +94,6 @@ input {
.subtitle {
/* font-weight: 500; */
font-size: 1rem;
color: $placeholder-color;
line-height: 1.25;
}
@ -103,7 +102,8 @@ input { @@ -103,7 +102,8 @@ input {
.page-authCode .input-wrapper,
.page-signUp .input-wrapper,
.page-password .input-wrapper {
margin-top: 3rem;
/* margin-top: 3rem; */
margin-top: 50px;
}
.page-authCode {
@ -281,7 +281,7 @@ input { @@ -281,7 +281,7 @@ input {
&[class*=" tgico-"] {
line-height: 52px;
font-size: 1.5rem;
font-size: 28px;
}
path {
@ -496,6 +496,14 @@ input { @@ -496,6 +496,14 @@ input {
}
}
.page-sign {
.auth-image {
width: 160px;
height: 160px;
margin-bottom: 45px;
}
}
.page-signUp {
.auth-image {
border-radius: 50%;
@ -524,7 +532,7 @@ input { @@ -524,7 +532,7 @@ input {
}
.input-wrapper {
width: 350px;
width: 360px;
margin: 0 auto;
}
@ -536,7 +544,7 @@ input { @@ -536,7 +544,7 @@ input {
content: " ";
top: 50%;
bottom: 0;
right: 15px;
right: 21px;
cursor: pointer;
height: 0;
@ -546,12 +554,12 @@ input { @@ -546,12 +554,12 @@ input {
border-radius: 1px;
border-width: 0 2px 2px 0;
display: inline-block;
padding: 4px;
padding: 5px;
vertical-align: middle;
z-index: 2;
margin-top: -7px;
margin-top: -9px;
transform: rotate(45deg);
-webkit-transform: rotate(45deg);
transition: .2s all;
@ -560,13 +568,12 @@ input { @@ -560,13 +568,12 @@ input {
label {
position: absolute;
color: $placeholder-color;
left: 12.5px;
left: 1rem;
right: auto;
z-index: 2;
top: 50%;
transform: translateY(-50%);
background-color: #fff;
font-size: 0.85rem;
transition: .2s all, .1s opacity;
display: inline-block;
cursor: text;
@ -574,12 +581,11 @@ input { @@ -574,12 +581,11 @@ input {
input {
border: 1px solid #DADCE0;
border-radius: $border-radius;
padding: 0 12.5px;
border-radius: $border-radius-big;
padding: 0 1rem;
box-sizing: border-box;
font-size: 0.85rem;
width: 100%;
height: 45px;
height: 54px;
transition: .2s border-color;
position: relative;
z-index: 1;
@ -587,8 +593,8 @@ input { @@ -587,8 +593,8 @@ input {
&:focus {
border-color: $button-primary-background;
border-width: 1.5px;
padding: 0 12px;
border-width: 2px;
padding: 0 calc(1rem - 1px);
}
&:disabled {
@ -606,7 +612,7 @@ input { @@ -606,7 +612,7 @@ input {
}
&:focus ~ .arrow-down {
margin-top: -2px;
margin-top: -4px;
transform: rotate(225deg);
-webkit-transform: rotate(225deg);
border-color: $button-primary-background;
@ -617,21 +623,21 @@ input { @@ -617,21 +623,21 @@ input {
}
&:focus + label, &:valid + label, &:disabled + label {
top: -8.5px;
top: -.5rem;
transform: none;
padding: 0 5px;
left: 7.5px;
font-size: 0.85rem!important;
left: .75rem;
font-size: 0.75rem!important;
opacity: 1;
}
}
}
.checkbox-field {
margin: 1rem 0;
margin: 1.25rem 0;
display: block;
text-align: left;
padding: 0 1rem;
padding: 0 19px;
/* font-weight: 500; */
position: relative;
}
@ -646,12 +652,11 @@ input { @@ -646,12 +652,11 @@ input {
& + span {
position: relative;
padding-left: 35px;
padding-left: calc(18px + 2.25rem);
cursor: pointer;
display: inline-block;
height: 25px;
line-height: 25px;
font-size: 1rem;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
@ -718,7 +723,7 @@ input { @@ -718,7 +723,7 @@ input {
}
.input-wrapper > * ~ * {
margin-top: 1.2rem;
margin-top: 1.5rem;
}
.select-wrapper {
@ -726,7 +731,7 @@ input { @@ -726,7 +731,7 @@ input {
/* height: auto; */
position: absolute;
width: 100%;
top: calc(100% + 10px);
top: calc(100% + .5rem);
left: 0;
overflow: hidden;
background-color: #fff;
@ -737,15 +742,15 @@ input { @@ -737,15 +742,15 @@ input {
flex-wrap: wrap;
ul {
margin: 10px 0;
margin: .5rem 0;
}
li {
/* display: flex; */
align-items: center;
padding: 0 12.5px;
padding: 0 1rem;
justify-content: space-between;
height: 50px;
height: 3.5rem;
cursor: pointer;
font-weight: 500;
@ -850,8 +855,7 @@ input:focus, button:focus { @@ -850,8 +855,7 @@ input:focus, button:focus {
border-radius: $border-radius;
width: 100%;
text-align: center;
font-size: 0.85rem;
height: 45px;
height: 54px;
border: none;
font-weight: 500;
cursor: pointer;
@ -1385,7 +1389,7 @@ div.scrollable::-webkit-scrollbar-thumb { @@ -1385,7 +1389,7 @@ div.scrollable::-webkit-scrollbar-thumb {
text-align: center;
flex: 1;
user-select: none;
font-size: 16px;
font-size: 1rem;
&.active {
position: relative;

Loading…
Cancel
Save