Browse Source

GIFs search

GIFs autoplay in panel
Fixed (not) bubble time (edited, etc)
master
morethanwords 4 years ago
parent
commit
489ecb974b
  1. 5
      package.json
  2. 2
      src/components/avatar.ts
  3. 174
      src/components/emoticonsDropdown.ts
  4. 129
      src/components/gifsMasonry.ts
  5. 105
      src/components/sidebarRight/gifs.ts
  6. 130
      src/components/sidebarRight/pollResults.ts
  7. 235
      src/components/sidebarRight/stickers.ts
  8. 2
      src/components/wrappers.ts
  9. 6
      src/index.hbs
  10. 132
      src/lib/appManagers/AppInlineBotsManager.ts
  11. 360
      src/lib/appManagers/appSidebarRight.ts
  12. 17
      src/lib/appManagers/appUsersManager.ts
  13. 35
      src/lib/mtproto/mtproto.service.ts
  14. 10
      src/scss/partials/_chatBubble.scss
  15. 29
      src/scss/partials/_emojiDropdown.scss
  16. 26
      src/scss/partials/_gifsMasonry.scss
  17. 1
      src/scss/style.scss
  18. 3
      src/types.d.ts

5
package.json

@ -5,12 +5,13 @@ @@ -5,12 +5,13 @@
"main": "index.js",
"scripts": {
"start": "webpack-dev-server --config webpack.dev.js",
"start-production": "webpack-dev-server --config webpack.prod.js",
"start:production": "webpack-dev-server --config webpack.prod.js",
"serve": "npm run build; node server.js",
"build": "webpack --config webpack.prod.js",
"build:dev": "webpack --config webpack.dev.js",
"test": "jest --config=jest.config.js",
"profile": "webpack --profile --json > stats.json --config webpack.prod.js",
"profile-dev": "webpack --profile --json > stats.json --config webpack.dev.js"
"profile:dev": "webpack --profile --json > stats.json --config webpack.dev.js"
},
"author": "",
"license": "ISC",

2
src/components/avatar.ts

@ -6,7 +6,7 @@ $rootScope.$on('avatar_update', (e: CustomEvent) => { @@ -6,7 +6,7 @@ $rootScope.$on('avatar_update', (e: CustomEvent) => {
appProfileManager.removeFromAvatarsCache(peerID);
(Array.from(document.querySelectorAll('avatar-element[peer="' + peerID + '"]')) as AvatarElement[]).forEach(elem => {
console.log('updating avatar:', elem);
//console.log('updating avatar:', elem);
elem.update();
});
});

174
src/components/emoticonsDropdown.ts

@ -18,6 +18,7 @@ import animationIntersector from "./animationIntersector"; @@ -18,6 +18,7 @@ import animationIntersector from "./animationIntersector";
import appSidebarRight from "../lib/appManagers/appSidebarRight";
import appStateManager from "../lib/appManagers/appStateManager";
import { horizontalMenu } from "./horizontalMenu";
import GifsMasonry from "./gifsMasonry";
export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown';
@ -329,7 +330,7 @@ class StickersTab implements EmoticonsTab { @@ -329,7 +330,7 @@ class StickersTab implements EmoticonsTab {
loop: false, */
lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue,
group: EMOTICONSSTICKERGROUP,
onlyThumb: true
onlyThumb: doc.sticker == 2
});
return div;
@ -535,18 +536,13 @@ class GifsTab implements EmoticonsTab { @@ -535,18 +536,13 @@ class GifsTab implements EmoticonsTab {
init() {
this.content = document.getElementById('content-gifs');
const masonry = this.content.firstElementChild as HTMLDivElement;
masonry.addEventListener('click', EmoticonsDropdown.onMediaClick);
const gifsContainer = this.content.firstElementChild as HTMLDivElement;
gifsContainer.addEventListener('click', EmoticonsDropdown.onMediaClick);
const masonry = new GifsMasonry(gifsContainer);
const scroll = new Scrollable(this.content, 'y', 'GIFS', null);
const preloader = putPreloader(this.content, true);
const width = 400;
const maxSingleWidth = width - 100;
const height = 100;
apiManager.invokeApi('messages.getSavedGifs', {hash: 0}).then((_res) => {
let res = _res as {
_: 'messages.savedGifs',
@ -557,155 +553,11 @@ class GifsTab implements EmoticonsTab { @@ -557,155 +553,11 @@ class GifsTab implements EmoticonsTab {
//let line: MTDocument[] = [];
let wastedWidth = 0;
res.gifs.forEach((gif, idx) => {
res.gifs[idx] = appDocsManager.saveDoc(gif);
});
preloader.remove();
for(let i = 0, length = res.gifs.length; i < length;) {
const doc = res.gifs[i];
let gifWidth = doc.w;
let gifHeight = doc.h;
if(gifHeight < height) {
gifWidth = height / gifHeight * gifWidth;
gifHeight = height;
}
let willUseWidth = Math.min(maxSingleWidth, width - wastedWidth, gifWidth);
let {w, h} = calcImageInBox(gifWidth, gifHeight, willUseWidth, height);
/* wastedWidth += w;
if(wastedWidth == width || h < height) {
wastedWidth = 0;
console.log('completed line', i, line);
line = [];
continue;
}
line.push(gif); */
++i;
//console.log('gif:', gif, w, h);
let div = document.createElement('div');
div.classList.add('gif', 'fade-in-transition');
div.style.width = w + 'px';
div.style.opacity = '0';
//div.style.height = h + 'px';
div.dataset.docID = doc.id;
masonry.append(div);
//let preloader = new ProgressivePreloader(div);
const posterURL = appDocsManager.getThumbURL(doc, false);
let img: HTMLImageElement;
if(posterURL) {
img = new Image();
img.src = posterURL;
}
let mouseOut = false;
const onMouseOver = (e: MouseEvent) => {
//console.log('onMouseOver', doc.id);
//cancelEvent(e);
mouseOut = false;
wrapVideo({
doc,
container: div,
//lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue,
//group: EMOTICONSSTICKERGROUP,
noInfo: true,
});
const video = div.querySelector('video');
video.addEventListener('canplay', () => {
div.style.opacity = '';
if(!mouseOut) {
img && img.classList.add('hide');
} else {
img && img.classList.remove('hide');
if(div.lastElementChild != img) {
div.lastElementChild.remove();
}
}
}, {once: true});
};
const afterRender = () => {
if(img) {
div.append(img);
div.style.opacity = '';
}
div.addEventListener('mouseover', onMouseOver, {once: true});
div.addEventListener('mouseout', (e) => {
const toElement = (e as any).toElement as Element;
//console.log('onMouseOut', doc.id, e);
if(findUpClassName(toElement, 'gif') == div) {
return;
}
//cancelEvent(e);
mouseOut = true;
const cb = () => {
if(div.lastElementChild != img) {
div.lastElementChild.remove();
}
div.addEventListener('mouseover', onMouseOver, {once: true});
};
img && img.classList.remove('hide');
/* window.requestAnimationFrame(() => {
window.requestAnimationFrame();
}); */
if(img) window.requestAnimationFrame(() => window.requestAnimationFrame(cb));
else cb();
});
};
(posterURL ? renderImageFromUrl(img, posterURL, afterRender) : afterRender());
/* wrapVideo({
doc,
container: div,
lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue,
group: EMOTICONSSTICKERGROUP,
noInfo: true,
}); */
/* EmoticonsDropdown.lazyLoadQueue.push({
div,
load: () => {
const download = appDocsManager.downloadDocNew(doc);
let thumbSize: string, posterURL: string;
if(doc.thumbs?.length) {
thumbSize = doc.thumbs[0].type;
posterURL = appDocsManager.getThumbURL(doc, thumbSize);
}
preloader.attach(div, true, appDocsManager.getInputFileName(doc, thumbSize));
download.promise.then(blob => {
preloader.detach();
div.innerHTML = `<video autoplay="true" muted="true" loop="true" src="${doc.url}" poster="${posterURL}" type="${doc.mime_type}"></video>`;
});
return download.promise;
}
}); */
}
res.gifs.forEach((doc, idx) => {
res.gifs[idx] = appDocsManager.saveDoc(doc);
masonry.add(doc, EMOTICONSSTICKERGROUP, EmoticonsDropdown.lazyLoadQueue);
});
});
this.init = null;
@ -797,7 +649,7 @@ class EmoticonsDropdown { @@ -797,7 +649,7 @@ class EmoticonsDropdown {
animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP);
this.tabID = id;
this.searchButton.classList.toggle('hide', this.tabID != 1);
this.searchButton.classList.toggle('hide', this.tabID == 0);
this.deleteBtn.classList.toggle('hide', this.tabID != 0);
}, () => {
const tab = this.tabs[this.tabID];
@ -811,7 +663,11 @@ class EmoticonsDropdown { @@ -811,7 +663,11 @@ class EmoticonsDropdown {
this.searchButton = this.element.querySelector('.emoji-tabs-search');
this.searchButton.addEventListener('click', () => {
appSidebarRight.stickersTab.init();
if(this.tabID == 1) {
appSidebarRight.stickersTab.init();
} else {
appSidebarRight.gifsTab.init();
}
});
this.deleteBtn = this.element.querySelector('.emoji-tabs-delete');

129
src/components/gifsMasonry.ts

@ -0,0 +1,129 @@ @@ -0,0 +1,129 @@
import { MTDocument } from "../types";
import { calcImageInBox, findUpClassName } from "../lib/utils";
import appDocsManager from "../lib/appManagers/appDocsManager";
import { wrapVideo } from "./wrappers";
import { renderImageFromUrl } from "./misc";
import LazyLoadQueue from "./lazyLoadQueue";
const width = 400;
const maxSingleWidth = width - 100;
const height = 100;
export default class GifsMasonry {
constructor(private element: HTMLElement) {
}
public add(doc: MTDocument, group: string, lazyLoadQueue?: LazyLoadQueue) {
let gifWidth = doc.w;
let gifHeight = doc.h;
if(gifHeight < height) {
gifWidth = height / gifHeight * gifWidth;
gifHeight = height;
}
let willUseWidth = Math.min(maxSingleWidth, width, gifWidth);
let {w, h} = calcImageInBox(gifWidth, gifHeight, willUseWidth, height);
/* wastedWidth += w;
if(wastedWidth == width || h < height) {
wastedWidth = 0;
console.log('completed line', i, line);
line = [];
continue;
}
line.push(gif); */
//console.log('gif:', gif, w, h);
let div = document.createElement('div');
div.classList.add('gif', 'fade-in-transition');
div.style.width = w + 'px';
div.style.opacity = '0';
//div.style.height = h + 'px';
div.dataset.docID = doc.id;
this.element.append(div);
//let preloader = new ProgressivePreloader(div);
const posterURL = appDocsManager.getThumbURL(doc, false);
let img: HTMLImageElement;
if(posterURL) {
img = new Image();
img.src = posterURL;
}
let mouseOut = false;
const onMouseOver = (/* e: MouseEvent */) => {
//console.log('onMouseOver', doc.id);
//cancelEvent(e);
mouseOut = false;
wrapVideo({
doc,
container: div,
lazyLoadQueue,
//lazyLoadQueue: EmoticonsDropdown.lazyLoadQueue,
group,
noInfo: true,
});
const video = div.querySelector('video');
video.addEventListener('canplay', () => {
div.style.opacity = '';
if(!mouseOut) {
img && img.classList.add('hide');
} else {
img && img.classList.remove('hide');
if(div.lastElementChild != img) {
div.lastElementChild.remove();
}
}
}, {once: true});
};
const afterRender = () => {
if(img) {
div.append(img);
div.style.opacity = '';
}
if(lazyLoadQueue) {
onMouseOver();
} else {
div.addEventListener('mouseover', onMouseOver, {once: true});
div.addEventListener('mouseout', (e) => {
const toElement = (e as any).toElement as Element;
//console.log('onMouseOut', doc.id, e);
if(findUpClassName(toElement, 'gif') == div) {
return;
}
//cancelEvent(e);
mouseOut = true;
const cb = () => {
if(div.lastElementChild != img) {
div.lastElementChild.remove();
}
div.addEventListener('mouseover', onMouseOver, {once: true});
};
img && img.classList.remove('hide');
/* window.requestAnimationFrame(() => {
window.requestAnimationFrame();
}); */
if(img) window.requestAnimationFrame(() => window.requestAnimationFrame(cb));
else cb();
});
}
};
(posterURL ? renderImageFromUrl(img, posterURL, afterRender) : afterRender());
}
}

105
src/components/sidebarRight/gifs.ts

@ -0,0 +1,105 @@ @@ -0,0 +1,105 @@
import { SliderTab } from "../slider";
import SearchInput from "../searchInput";
import Scrollable from "../scrollable_new";
import LazyLoadQueue from "../lazyLoadQueue";
import animationIntersector from "../animationIntersector";
import appSidebarRight, { AppSidebarRight } from "../../lib/appManagers/appSidebarRight";
import appUsersManager, { User } from "../../lib/appManagers/appUsersManager";
import appInlineBotsManager, { AppInlineBotsManager } from "../../lib/appManagers/AppInlineBotsManager";
import GifsMasonry from "../gifsMasonry";
const ANIMATIONGROUP = 'GIFS-SEARCH';
export default class AppGifsTab implements SliderTab {
private container = document.getElementById('search-gifs-container') as HTMLDivElement;
private contentDiv = this.container.querySelector('.sidebar-content') as HTMLDivElement;
private backBtn = this.container.querySelector('.sidebar-close-button') as HTMLButtonElement;
//private input = this.container.querySelector('#stickers-search') as HTMLInputElement;
private searchInput: SearchInput;
private gifsDiv = this.contentDiv.firstElementChild as HTMLDivElement;
private scrollable: Scrollable;
private lazyLoadQueue: LazyLoadQueue;
private nextOffset = '';
private gifBotPeerID: number;
private masonry: GifsMasonry;
private searchPromise: ReturnType<AppInlineBotsManager['getInlineResults']>;
constructor() {
this.scrollable = new Scrollable(this.contentDiv, 'y', ANIMATIONGROUP, undefined, undefined, 2);
this.scrollable.setVirtualContainer(this.gifsDiv);
this.masonry = new GifsMasonry(this.gifsDiv);
this.lazyLoadQueue = new LazyLoadQueue();
this.searchInput = new SearchInput('Search GIFs', (value) => {
this.reset();
this.search(value);
});
this.scrollable.onScrolledBottom = () => {
this.search(this.searchInput.value, false);
};
this.backBtn.parentElement.append(this.searchInput.container);
}
public onCloseAfterTimeout() {
this.reset();
this.gifsDiv.innerHTML = '';
this.searchInput.value = '';
animationIntersector.checkAnimations(undefined, ANIMATIONGROUP);
}
private reset() {
this.searchPromise = null;
this.nextOffset = '';
this.lazyLoadQueue.clear();
}
public init() {
appSidebarRight.selectTab(AppSidebarRight.SLIDERITEMSIDS.gifs);
appSidebarRight.toggleSidebar(true).then(() => {
//this.renderFeatured();
this.search('', true);
this.reset();
});
}
public async search(query: string, newSearch = true) {
if(this.searchPromise) return;
if(!this.gifBotPeerID) {
this.gifBotPeerID = (await appUsersManager.resolveUsername('gif')).id;
}
try {
this.searchPromise = appInlineBotsManager.getInlineResults(0, this.gifBotPeerID, query, this.nextOffset);
const { results, next_offset } = await this.searchPromise;
if(this.searchInput.value != query) {
return;
}
this.searchPromise = null;
this.nextOffset = next_offset;
if(newSearch) {
this.gifsDiv.innerHTML = '';
}
results.forEach((result) => {
if(result._ === 'botInlineMediaResult' && result.document) {
this.masonry.add(result.document, ANIMATIONGROUP, this.lazyLoadQueue);
}
});
this.scrollable.onScroll();
} catch (err) {
throw new Error(JSON.stringify(err));
}
}
}

130
src/components/sidebarRight/pollResults.ts

@ -0,0 +1,130 @@ @@ -0,0 +1,130 @@
import { SliderTab } from "../slider";
import Scrollable from "../scrollable_new";
import appSidebarRight, { AppSidebarRight } from "../../lib/appManagers/appSidebarRight";
import appPollsManager from "../../lib/appManagers/appPollsManager";
import { roundPercents } from "../poll";
import { RichTextProcessor } from "../../lib/richtextprocessor";
import appDialogsManager from "../../lib/appManagers/appDialogsManager";
import { ripple } from "../ripple";
export default class AppPollResultsTab implements SliderTab {
private container = document.getElementById('poll-results-container') as HTMLDivElement;
private contentDiv = this.container.querySelector('.sidebar-content') as HTMLDivElement;
private resultsDiv = this.contentDiv.firstElementChild as HTMLDivElement;
private scrollable: Scrollable;
private pollID: string;
private mid: number;
constructor() {
this.scrollable = new Scrollable(this.contentDiv, 'y', 'POLL-RESULTS', undefined, undefined, 2);
}
public cleanup() {
this.resultsDiv.innerHTML = '';
this.pollID = '';
this.mid = 0;
}
public onCloseAfterTimeout() {
this.cleanup();
}
public init(pollID: string, mid: number) {
if(this.pollID == pollID && this.mid == mid) return;
this.cleanup();
this.pollID = pollID;
this.mid = mid;
appSidebarRight.selectTab(AppSidebarRight.SLIDERITEMSIDS.pollResults);
const poll = appPollsManager.getPoll(pollID);
const title = document.createElement('h3');
title.innerHTML = poll.poll.rQuestion;
const percents = poll.results.results.map(v => v.voters / poll.results.total_voters * 100);
roundPercents(percents);
const fragment = document.createDocumentFragment();
poll.results.results.forEach((result, idx) => {
if(!result.voters) return;
const hr = document.createElement('hr');
const answer = poll.poll.answers[idx];
// Head
const answerEl = document.createElement('div');
answerEl.classList.add('poll-results-answer');
const answerTitle = document.createElement('div');
answerTitle.innerHTML = RichTextProcessor.wrapEmojiText(answer.text);
const answerPercents = document.createElement('div');
answerPercents.innerText = Math.round(percents[idx]) + '%';
answerEl.append(answerTitle, answerPercents);
// Humans
const list = document.createElement('ul');
list.classList.add('poll-results-voters');
appDialogsManager.setListClickListener(list);
list.style.minHeight = Math.min(result.voters, 4) * 50 + 'px';
fragment.append(hr, answerEl, list);
let offset: string, limit = 4, loading = false, left = result.voters - 4;
const load = () => {
if(loading) return;
loading = true;
appPollsManager.getVotes(mid, answer.option, offset, limit).then(votesList => {
votesList.votes.forEach(vote => {
const {dom} = appDialogsManager.addDialog(vote.user_id, list, false, false, undefined, false);
dom.lastMessageSpan.parentElement.remove();
});
if(offset) {
left -= votesList.votes.length;
(showMore.lastElementChild as HTMLElement).innerText = `Show ${Math.min(20, left)} more voter${left > 1 ? 's' : ''}`;
}
offset = votesList.next_offset;
limit = 20;
if(!left || !votesList.votes.length) {
showMore.remove();
}
}).finally(() => {
loading = false;
});
};
load();
if(left <= 0) return;
const showMore = document.createElement('div');
showMore.classList.add('poll-results-more', 'show-more');
showMore.addEventListener('click', load);
showMore.innerHTML = `<div class="tgico-down"></div><div>Show ${Math.min(20, left)} more voter${left > 1 ? 's' : ''}</div>`;
ripple(showMore);
fragment.append(showMore);
});
this.resultsDiv.append(title, fragment);
appSidebarRight.toggleSidebar(true).then(() => {
/* appPollsManager.getVotes(mid).then(votes => {
console.log('gOt VotEs', votes);
}); */
});
}
}

235
src/components/sidebarRight/stickers.ts

@ -0,0 +1,235 @@ @@ -0,0 +1,235 @@
import { SliderTab } from "../slider";
import SearchInput from "../searchInput";
import Scrollable from "../scrollable_new";
import LazyLoadQueue from "../lazyLoadQueue";
import { findUpClassName } from "../../lib/utils";
import appImManager from "../../lib/appManagers/appImManager";
import appStickersManager, { MTStickerSet, MTStickerSetCovered, MTStickerSetMultiCovered } from "../../lib/appManagers/appStickersManager";
import PopupStickers from "../popupStickers";
import animationIntersector from "../animationIntersector";
import { RichTextProcessor } from "../../lib/richtextprocessor";
import { wrapSticker } from "../wrappers";
import appSidebarRight, { AppSidebarRight } from "../../lib/appManagers/appSidebarRight";
export default class AppStickersTab implements SliderTab {
private container = document.getElementById('stickers-container') as HTMLDivElement;
private contentDiv = this.container.querySelector('.sidebar-content') as HTMLDivElement;
private backBtn = this.container.querySelector('.sidebar-close-button') as HTMLButtonElement;
//private input = this.container.querySelector('#stickers-search') as HTMLInputElement;
private searchInput: SearchInput;
private setsDiv = this.contentDiv.firstElementChild as HTMLDivElement;
private scrollable: Scrollable;
private lazyLoadQueue: LazyLoadQueue;
constructor() {
this.scrollable = new Scrollable(this.contentDiv, 'y', 'STICKERS-SEARCH', undefined, undefined, 2);
this.scrollable.setVirtualContainer(this.setsDiv);
this.lazyLoadQueue = new LazyLoadQueue();
this.searchInput = new SearchInput('Search Stickers', (value) => {
this.search(value);
});
this.backBtn.parentElement.append(this.searchInput.container);
this.setsDiv.addEventListener('click', (e) => {
const sticker = findUpClassName(e.target, 'sticker-set-sticker');
if(sticker) {
const docID = sticker.dataset.docID;
appImManager.chatInputC.sendMessageWithDocument(docID);
return;
}
const target = findUpClassName(e.target, 'sticker-set');
if(!target) return;
const id = target.dataset.stickerSet as string;
const access_hash = target.dataset.stickerSet as string;
const button = findUpClassName(e.target, 'sticker-set-button') as HTMLElement;
if(button) {
e.preventDefault();
e.cancelBubble = true;
button.setAttribute('disabled', 'true');
appStickersManager.getStickerSet({id, access_hash}).then(full => {
appStickersManager.toggleStickerSet(full.set).then(changed => {
if(changed) {
button.innerText = full.set.installed_date ? 'Added' : 'Add';
button.classList.toggle('gray', !!full.set.installed_date);
}
}).finally(() => {
//button.style.width = set.installed_date ? '68px' : '52px';
button.removeAttribute('disabled');
});
});
} else {
appStickersManager.getStickerSet({id, access_hash}).then(full => {
new PopupStickers(full.set).show();
});
}
});
}
public onCloseAfterTimeout() {
this.setsDiv.innerHTML = '';
this.searchInput.value = '';
animationIntersector.checkAnimations(undefined, 'STICKERS-SEARCH');
}
public renderSet(set: MTStickerSet) {
//console.log('renderSet:', set);
const div = document.createElement('div');
div.classList.add('sticker-set');
const header = document.createElement('div');
header.classList.add('sticker-set-header');
const details = document.createElement('div');
details.classList.add('sticker-set-details');
details.innerHTML = `
<div class="sticker-set-name">${RichTextProcessor.wrapEmojiText(set.title)}</div>
<div class="sticker-set-count">${set.count} stickers</div>
`;
const button = document.createElement('button');
button.classList.add('btn-primary', 'sticker-set-button');
button.innerText = set.installed_date ? 'Added' : 'Add';
// button.style.width = set.installed_date ? '68px' : '52px';
if(set.installed_date) {
button.classList.add('gray');
}
//ripple(button);
header.append(details, button);
const stickersDiv = document.createElement('div');
stickersDiv.classList.add('sticker-set-stickers');
const count = Math.min(5, set.count);
for(let i = 0; i < count; ++i) {
const stickerDiv = document.createElement('div');
stickerDiv.classList.add('sticker-set-sticker');
stickersDiv.append(stickerDiv);
}
appStickersManager.getStickerSet(set).then(set => {
//console.log('renderSet got set:', set);
for(let i = 0; i < count; ++i) {
const div = stickersDiv.children[i] as HTMLDivElement;
wrapSticker({
doc: set.documents[i],
div,
lazyLoadQueue: this.lazyLoadQueue,
group: 'STICKERS-SEARCH',
/* play: false,
loop: false, */
play: true,
loop: true,
width: 68,
height: 68
});
}
});
/* const onMouseOver = () => {
const animations: AnimationItem['animation'][] = [];
for(let i = 0; i < count; ++i) {
const stickerDiv = stickersDiv.children[i] as HTMLElement;
const animationItem = animationIntersector.getAnimation(stickerDiv);
if(!animationItem) continue;
const animation = animationItem.animation;
animations.push(animation);
animation.loop = true;
animation.play();
}
div.addEventListener('mouseout', () => {
animations.forEach(animation => {
animation.loop = false;
});
div.addEventListener('mouseover', onMouseOver, {once: true});
}, {once: true});
};
div.addEventListener('mouseover', onMouseOver, {once: true}); */
div.dataset.stickerSet = set.id;
div.dataset.access_hash = set.access_hash;
div.dataset.title = set.title;
div.append(header, stickersDiv);
this.scrollable.append(div);
}
public init() {
appSidebarRight.selectTab(AppSidebarRight.SLIDERITEMSIDS.stickers);
appSidebarRight.toggleSidebar(true).then(() => {
this.renderFeatured();
});
}
public renderFeatured() {
return appStickersManager.getFeaturedStickers().then(coveredSets => {
if(this.searchInput.value) {
return;
}
coveredSets = this.filterRendered('', coveredSets);
coveredSets.forEach(set => {
this.renderSet(set.set);
});
});
}
private filterRendered(query: string, coveredSets: (MTStickerSetCovered | MTStickerSetMultiCovered)[]) {
coveredSets = coveredSets.slice();
const children = Array.from(this.setsDiv.children) as HTMLElement[];
children.forEachReverse(el => {
const id = el.dataset.stickerSet;
const index = coveredSets.findIndex(covered => covered.set.id == id);
if(index !== -1) {
coveredSets.splice(index, 1);
} else if(!query || !el.dataset.title.toLowerCase().includes(query.toLowerCase())) {
el.remove();
}
});
animationIntersector.checkAnimations(undefined, 'STICKERS-SEARCH');
return coveredSets;
}
public search(query: string) {
if(!query) {
return this.renderFeatured();
}
return appStickersManager.searchStickerSets(query, false).then(coveredSets => {
if(this.searchInput.value != query) {
return;
}
//console.log('search result:', coveredSets);
coveredSets = this.filterRendered(query, coveredSets);
coveredSets.forEach(set => {
this.renderSet(set.set);
});
});
}
}

2
src/components/wrappers.ts

@ -446,7 +446,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -446,7 +446,7 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
}
};
if(thumb.bytes) {
if(thumb.bytes || thumb.url) {
img = new Image();
if((!isSafari || doc.stickerThumbConverted)/* && false */) {

6
src/index.hbs

@ -672,6 +672,12 @@ @@ -672,6 +672,12 @@
</div>
<div class="sidebar-content"><div class="poll-results"></div></div>
</div>
<div class="sidebar-slider-item sidebar-search chats-container" id="search-gifs-container">
<div class="sidebar-header">
<button class="btn-icon rp tgico-close sidebar-close-button"></button>
</div>
<div class="sidebar-content"><div class="gifs-masonry"></div></div>
</div>
</div>
</div>
</div>

132
src/lib/appManagers/AppInlineBotsManager.ts

@ -4,11 +4,99 @@ import appPeersManager from "../appManagers/appPeersManager"; @@ -4,11 +4,99 @@ import appPeersManager from "../appManagers/appPeersManager";
import appMessagesIDsManager from "./appMessagesIDsManager";
import { RichTextProcessor } from "../richtextprocessor";
import { toast } from "../../components/toast";
import appUsersManager, { User } from "./appUsersManager";
import appPhotosManager, { MTPhoto } from "./appPhotosManager";
import { MTDocument } from "../../types";
import appDocsManager from "./appDocsManager";
type botInlineResult = {
_: 'botInlineResult',
flags: number,
id: string,
type: string,
title?: string,
description?: string,
url?: string,
thumb: any,
content: any,
send_message: any
};
type botInlineMediaResult = {
_: 'botInlineMediaResult',
flags: number,
id: string,
type: string,
photo?: MTPhoto,
document?: MTDocument,
title?: string,
description?: string,
send_message: any
};
type BotInlineResult = (botInlineResult | botInlineMediaResult) & Partial<{
qID: string,
botID: number,
rTitle: string,
rDescription: string,
initials: string
}>;
export class AppInlineBotsManager {
/* private inlineResults: any = {};
private inlineResults: {[qID: string]: BotInlineResult} = {};
public getInlineResults(peerID: number, botID: number, query = '', offset = '', geo?: any) {
return apiManagerProxy.invokeApi('messages.getInlineBotResults', {
flags: 0 | (geo ? 1 : 0),
bot: appUsersManager.getUserInput(botID),
peer: appPeersManager.getInputPeerByID(peerID),
query: query,
geo_point: geo && {_: 'inputGeoPoint', lat: geo['lat'], long: geo['long']},
offset
}, {timeout: 1, stopTime: -1, noErrorBox: true}).then((botResults: {
_: 'messages.botResults',
flags: number,
pFlags: Partial<{gallery: true}>,
query_id: string,
next_offset?: string,
switch_pm?: any,
results: BotInlineResult[],
cache_time: number,
users: User[]
}) => {
const queryID = botResults.query_id;
/* delete botResults._;
delete botResults.flags;
delete botResults.query_id; */
if(botResults.switch_pm) {
botResults.switch_pm.rText = RichTextProcessor.wrapRichText(botResults.switch_pm.text, {noLinebreaks: true, noLinks: true});
}
botResults.results.forEach((result: BotInlineResult) => {
const qID = queryID + '_' + result.id;
result.qID = qID;
result.botID = botID;
result.rTitle = RichTextProcessor.wrapRichText(result.title, {noLinebreaks: true, noLinks: true});
result.rDescription = RichTextProcessor.wrapRichText(result.description, {noLinebreaks: true, noLinks: true});
result.initials = ((result as botInlineResult).url || result.title || result.type || '').substr(0, 1);
if(result._ == 'botInlineMediaResult') {
if(result.document) {
result.document = appDocsManager.saveDoc(result.document);
}
if(result.photo) {
result.photo = appPhotosManager.savePhoto(result.photo);
}
}
this.inlineResults[qID] = result;
});
return botResults;
});
}
function getPopularBots () {
/* function getPopularBots () {
return Storage.get('inline_bots_popular').then(function (bots) {
var result = []
var i, len
@ -91,46 +179,6 @@ export class AppInlineBotsManager { @@ -91,46 +179,6 @@ export class AppInlineBotsManager {
})
}
function getInlineResults (peerID, botID, query, geo, offset) {
return MtpApiManager.invokeApi('messages.getInlineBotResults', {
flags: 0 | (geo ? 1 : 0),
bot: AppUsersManager.getUserInput(botID),
peer: AppPeersManager.getInputPeerByID(peerID),
query: query,
geo_point: geo && {_: 'inputGeoPoint', lat: geo['lat'], long: geo['long']},
offset: offset
}, {timeout: 1, stopTime: -1, noErrorBox: true}).then(function (botResults) {
var queryID = botResults.query_id
delete botResults._
delete botResults.flags
delete botResults.query_id
if (botResults.switch_pm) {
botResults.switch_pm.rText = RichTextProcessor.wrapRichText(botResults.switch_pm.text, {noLinebreaks: true, noLinks: true})
}
angular.forEach(botResults.results, function (result) {
var qID = queryID + '_' + result.id
result.qID = qID
result.botID = botID
result.rTitle = RichTextProcessor.wrapRichText(result.title, {noLinebreaks: true, noLinks: true})
result.rDescription = RichTextProcessor.wrapRichText(result.description, {noLinebreaks: true, noLinks: true})
result.initials = (result.url || result.title || result.type || '').substr(0, 1)
if (result.document) {
AppDocsManager.saveDoc(result.document)
}
if (result.photo) {
AppPhotosManager.savePhoto(result.photo)
}
inlineResults[qID] = result
})
return botResults
})
}
function regroupWrappedResults (results, rowW, rowH) {
if (!results ||
!results[0] ||

360
src/lib/appManagers/appSidebarRight.ts

@ -18,15 +18,11 @@ import AvatarElement from "../../components/avatar"; @@ -18,15 +18,11 @@ import AvatarElement from "../../components/avatar";
import appForward from "../../components/appForward";
import { mediaSizes } from "../config";
import SidebarSlider, { SliderTab } from "../../components/slider";
import appStickersManager, { MTStickerSet, MTStickerSetCovered, MTStickerSetMultiCovered } from "./appStickersManager";
import animationIntersector from "../../components/animationIntersector";
import PopupStickers from "../../components/popupStickers";
import SearchInput from "../../components/searchInput";
import appPollsManager from "./appPollsManager";
import { roundPercents } from "../../components/poll";
import appDialogsManager from "./appDialogsManager";
import { ripple } from "../../components/ripple";
import { horizontalMenu } from "../../components/horizontalMenu";
import AppStickersTab from "../../components/sidebarRight/stickers";
import AppPollResultsTab from "../../components/sidebarRight/pollResults";
import AppGifsTab from "../../components/sidebarRight/gifs";
const testScroll = false;
@ -44,353 +40,9 @@ let setText = (text: string, el: HTMLDivElement) => { @@ -44,353 +40,9 @@ let setText = (text: string, el: HTMLDivElement) => {
});
};
class AppStickersTab implements SliderTab {
private container = document.getElementById('stickers-container') as HTMLDivElement;
private contentDiv = this.container.querySelector('.sidebar-content') as HTMLDivElement;
private backBtn = this.container.querySelector('.sidebar-close-button') as HTMLButtonElement;
//private input = this.container.querySelector('#stickers-search') as HTMLInputElement;
private searchInput: SearchInput;
private setsDiv = this.contentDiv.firstElementChild as HTMLDivElement;
private scrollable: Scrollable;
private lazyLoadQueue: LazyLoadQueue;
constructor() {
this.scrollable = new Scrollable(this.contentDiv, 'y', 'STICKERS-SEARCH', undefined, undefined, 2);
this.scrollable.setVirtualContainer(this.setsDiv);
this.lazyLoadQueue = new LazyLoadQueue();
this.searchInput = new SearchInput('Search Stickers', (value) => {
this.search(value);
});
this.backBtn.parentElement.append(this.searchInput.container);
this.setsDiv.addEventListener('click', (e) => {
const sticker = findUpClassName(e.target, 'sticker-set-sticker');
if(sticker) {
const docID = sticker.dataset.docID;
appImManager.chatInputC.sendMessageWithDocument(docID);
return;
}
const target = findUpClassName(e.target, 'sticker-set');
if(!target) return;
const id = target.dataset.stickerSet as string;
const access_hash = target.dataset.stickerSet as string;
const button = findUpClassName(e.target, 'sticker-set-button') as HTMLElement;
if(button) {
e.preventDefault();
e.cancelBubble = true;
button.setAttribute('disabled', 'true');
appStickersManager.getStickerSet({id, access_hash}).then(full => {
appStickersManager.toggleStickerSet(full.set).then(changed => {
if(changed) {
button.innerText = full.set.installed_date ? 'Added' : 'Add';
button.classList.toggle('gray', !!full.set.installed_date);
}
}).finally(() => {
//button.style.width = set.installed_date ? '68px' : '52px';
button.removeAttribute('disabled');
});
});
} else {
appStickersManager.getStickerSet({id, access_hash}).then(full => {
new PopupStickers(full.set).show();
});
}
});
}
public onCloseAfterTimeout() {
this.setsDiv.innerHTML = '';
this.searchInput.value = '';
animationIntersector.checkAnimations(undefined, 'STICKERS-SEARCH');
}
public renderSet(set: MTStickerSet) {
//console.log('renderSet:', set);
const div = document.createElement('div');
div.classList.add('sticker-set');
const header = document.createElement('div');
header.classList.add('sticker-set-header');
const details = document.createElement('div');
details.classList.add('sticker-set-details');
details.innerHTML = `
<div class="sticker-set-name">${RichTextProcessor.wrapEmojiText(set.title)}</div>
<div class="sticker-set-count">${set.count} stickers</div>
`;
const button = document.createElement('button');
button.classList.add('btn-primary', 'sticker-set-button');
button.innerText = set.installed_date ? 'Added' : 'Add';
// button.style.width = set.installed_date ? '68px' : '52px';
if(set.installed_date) {
button.classList.add('gray');
}
//ripple(button);
header.append(details, button);
const stickersDiv = document.createElement('div');
stickersDiv.classList.add('sticker-set-stickers');
const count = Math.min(5, set.count);
for(let i = 0; i < count; ++i) {
const stickerDiv = document.createElement('div');
stickerDiv.classList.add('sticker-set-sticker');
stickersDiv.append(stickerDiv);
}
appStickersManager.getStickerSet(set).then(set => {
//console.log('renderSet got set:', set);
for(let i = 0; i < count; ++i) {
const div = stickersDiv.children[i] as HTMLDivElement;
wrapSticker({
doc: set.documents[i],
div,
lazyLoadQueue: this.lazyLoadQueue,
group: 'STICKERS-SEARCH',
/* play: false,
loop: false, */
play: true,
loop: true,
width: 68,
height: 68
});
}
});
/* const onMouseOver = () => {
const animations: AnimationItem['animation'][] = [];
for(let i = 0; i < count; ++i) {
const stickerDiv = stickersDiv.children[i] as HTMLElement;
const animationItem = animationIntersector.getAnimation(stickerDiv);
if(!animationItem) continue;
const animation = animationItem.animation;
animations.push(animation);
animation.loop = true;
animation.play();
}
div.addEventListener('mouseout', () => {
animations.forEach(animation => {
animation.loop = false;
});
div.addEventListener('mouseover', onMouseOver, {once: true});
}, {once: true});
};
div.addEventListener('mouseover', onMouseOver, {once: true}); */
div.dataset.stickerSet = set.id;
div.dataset.access_hash = set.access_hash;
div.dataset.title = set.title;
div.append(header, stickersDiv);
this.scrollable.append(div);
}
public init() {
appSidebarRight.selectTab(AppSidebarRight.SLIDERITEMSIDS.stickers);
appSidebarRight.toggleSidebar(true).then(() => {
this.renderFeatured();
});
}
public renderFeatured() {
return appStickersManager.getFeaturedStickers().then(coveredSets => {
if(this.searchInput.value) {
return;
}
coveredSets = this.filterRendered('', coveredSets);
coveredSets.forEach(set => {
this.renderSet(set.set);
});
});
}
private filterRendered(query: string, coveredSets: (MTStickerSetCovered | MTStickerSetMultiCovered)[]) {
coveredSets = coveredSets.slice();
const children = Array.from(this.setsDiv.children) as HTMLElement[];
children.forEachReverse(el => {
const id = el.dataset.stickerSet;
const index = coveredSets.findIndex(covered => covered.set.id == id);
if(index !== -1) {
coveredSets.splice(index, 1);
} else if(!query || !el.dataset.title.toLowerCase().includes(query.toLowerCase())) {
el.remove();
}
});
animationIntersector.checkAnimations(undefined, 'STICKERS-SEARCH');
return coveredSets;
}
public search(query: string) {
if(!query) {
return this.renderFeatured();
}
return appStickersManager.searchStickerSets(query, false).then(coveredSets => {
if(this.searchInput.value != query) {
return;
}
//console.log('search result:', coveredSets);
coveredSets = this.filterRendered(query, coveredSets);
coveredSets.forEach(set => {
this.renderSet(set.set);
});
});
}
}
class AppPollResultsTab implements SliderTab {
private container = document.getElementById('poll-results-container') as HTMLDivElement;
private contentDiv = this.container.querySelector('.sidebar-content') as HTMLDivElement;
private resultsDiv = this.contentDiv.firstElementChild as HTMLDivElement;
private scrollable: Scrollable;
private pollID: string;
private mid: number;
constructor() {
this.scrollable = new Scrollable(this.contentDiv, 'y', 'POLL-RESULTS', undefined, undefined, 2);
}
public cleanup() {
this.resultsDiv.innerHTML = '';
this.pollID = '';
this.mid = 0;
}
public onCloseAfterTimeout() {
this.cleanup();
}
public init(pollID: string, mid: number) {
if(this.pollID == pollID && this.mid == mid) return;
this.cleanup();
this.pollID = pollID;
this.mid = mid;
appSidebarRight.selectTab(AppSidebarRight.SLIDERITEMSIDS.pollResults);
const poll = appPollsManager.getPoll(pollID);
const title = document.createElement('h3');
title.innerHTML = poll.poll.rQuestion;
const percents = poll.results.results.map(v => v.voters / poll.results.total_voters * 100);
roundPercents(percents);
const fragment = document.createDocumentFragment();
poll.results.results.forEach((result, idx) => {
if(!result.voters) return;
const hr = document.createElement('hr');
const answer = poll.poll.answers[idx];
// Head
const answerEl = document.createElement('div');
answerEl.classList.add('poll-results-answer');
const answerTitle = document.createElement('div');
answerTitle.innerHTML = RichTextProcessor.wrapEmojiText(answer.text);
const answerPercents = document.createElement('div');
answerPercents.innerText = Math.round(percents[idx]) + '%';
answerEl.append(answerTitle, answerPercents);
// Humans
const list = document.createElement('ul');
list.classList.add('poll-results-voters');
appDialogsManager.setListClickListener(list);
list.style.minHeight = Math.min(result.voters, 4) * 50 + 'px';
fragment.append(hr, answerEl, list);
let offset: string, limit = 4, loading = false, left = result.voters - 4;
const load = () => {
if(loading) return;
loading = true;
appPollsManager.getVotes(mid, answer.option, offset, limit).then(votesList => {
votesList.votes.forEach(vote => {
const {dom} = appDialogsManager.addDialog(vote.user_id, list, false, false, undefined, false);
dom.lastMessageSpan.parentElement.remove();
});
if(offset) {
left -= votesList.votes.length;
(showMore.lastElementChild as HTMLElement).innerText = `Show ${Math.min(20, left)} more voter${left > 1 ? 's' : ''}`;
}
offset = votesList.next_offset;
limit = 20;
if(!left || !votesList.votes.length) {
showMore.remove();
}
}).finally(() => {
loading = false;
});
};
load();
if(left <= 0) return;
const showMore = document.createElement('div');
showMore.classList.add('poll-results-more', 'show-more');
showMore.addEventListener('click', load);
showMore.innerHTML = `<div class="tgico-down"></div><div>Show ${Math.min(20, left)} more voter${left > 1 ? 's' : ''}</div>`;
ripple(showMore);
fragment.append(showMore);
});
this.resultsDiv.append(title, fragment);
appSidebarRight.toggleSidebar(true).then(() => {
/* appPollsManager.getVotes(mid).then(votes => {
console.log('gOt VotEs', votes);
}); */
});
}
}
const stickersTab = new AppStickersTab();
const pollResultsTab = new AppPollResultsTab();
const gifsTab = new AppGifsTab();
export class AppSidebarRight extends SidebarSlider {
public static SLIDERITEMSIDS = {
@ -398,6 +50,7 @@ export class AppSidebarRight extends SidebarSlider { @@ -398,6 +50,7 @@ export class AppSidebarRight extends SidebarSlider {
forward: 2,
stickers: 3,
pollResults: 4,
gifs: 5,
};
public profileContainer: HTMLDivElement;
@ -467,17 +120,20 @@ export class AppSidebarRight extends SidebarSlider { @@ -467,17 +120,20 @@ export class AppSidebarRight extends SidebarSlider {
public stickersTab: AppStickersTab;
public pollResultsTab: AppPollResultsTab;
public gifsTab: AppGifsTab;
constructor() {
super(document.getElementById('column-right') as HTMLElement, {
[AppSidebarRight.SLIDERITEMSIDS.stickers]: stickersTab,
[AppSidebarRight.SLIDERITEMSIDS.pollResults]: pollResultsTab,
[AppSidebarRight.SLIDERITEMSIDS.gifs]: gifsTab
});
//this._selectTab(3);
this.stickersTab = stickersTab;
this.pollResultsTab = pollResultsTab;
this.gifsTab = gifsTab;
this.profileContainer = this.sidebarEl.querySelector('.profile-container');
this.profileContentEl = this.sidebarEl.querySelector('.profile-content');

17
src/lib/appManagers/appUsersManager.ts

@ -133,6 +133,19 @@ export class AppUsersManager { @@ -133,6 +133,19 @@ export class AppUsersManager {
});
}
public async resolveUsername(username: string) {
if(this.usernames[username]) {
return this.users[this.usernames[username]];
}
return await apiManager.invokeApi('contacts.resolveUsername', {username}).then(resolvedPeer => {
this.saveApiUser(resolvedPeer.users[0]);
appChatsManager.saveApiChats(resolvedPeer.chats);
return this.users[this.usernames[username]];
});
}
public pushContact(userID: number) {
this.contactsList.add(userID);
searchIndexManager.indexObject(userID, this.getUserSearchText(userID), this.contactsIndex);
@ -183,9 +196,9 @@ export class AppUsersManager { @@ -183,9 +196,9 @@ export class AppUsersManager {
});
}
public resolveUsername(username: string) {
/* public resolveUsername(username: string) {
return this.usernames[username] || 0;
}
} */
public saveApiUsers(apiUsers: any[]) {
apiUsers.forEach((user) => this.saveApiUser(user));

35
src/lib/mtproto/mtproto.service.ts

@ -78,7 +78,7 @@ networkerFactory.setUpdatesProcessor((obj, bool) => { @@ -78,7 +78,7 @@ networkerFactory.setUpdatesProcessor((obj, bool) => {
notify({update: {obj, bool}});
});
ctx.addEventListener('message', async(e) => {
const onMessage = async(e: ExtendableMessageEvent) => {
const taskID = e.data.taskID;
log.debug('got message:', taskID, e, e.data);
@ -139,7 +139,9 @@ ctx.addEventListener('message', async(e) => { @@ -139,7 +139,9 @@ ctx.addEventListener('message', async(e) => {
//throw new Error('Unknown task: ' + e.data.task);
}
}
});
};
ctx.onmessage = onMessage;
/**
* Service Worker Installation
@ -195,14 +197,11 @@ function responseForSafariFirstRange(range: [number, number], mimeType: string, @@ -195,14 +197,11 @@ function responseForSafariFirstRange(range: [number, number], mimeType: string,
return null;
}
ctx.addEventListener('error', (error) => {
ctx.onerror = (error) => {
log.error('error:', error);
});
};
/**
* Fetch requests
*/
ctx.addEventListener('fetch', (event: FetchEvent): void => {
const onFetch = (event: FetchEvent): void => {
const [, url, scope, params] = /http[:s]+\/\/.*?(\/(.*?)(?:$|\/(.*)$))/.exec(event.request.url) || [];
log.debug('[fetch]:', event);
@ -440,11 +439,19 @@ ctx.addEventListener('fetch', (event: FetchEvent): void => { @@ -440,11 +439,19 @@ ctx.addEventListener('fetch', (event: FetchEvent): void => {
if (url && url.endsWith('.tgs')) event.respondWith(fetchTGS(url));
else event.respondWith(fetch(event.request.url)); */
}
});
};
/**
* Fetch requests
*/
//ctx.addEventListener('fetch', );
ctx.onfetch = onFetch;
const DOWNLOAD_CHUNK_LIMIT = 512 * 1024;
const STREAM_CHUNK_UPPER_LIMIT = 256 * 1024;
const SMALLEST_CHUNK_LIMIT = 256 * 4;
//const STREAM_CHUNK_UPPER_LIMIT = 256 * 1024;
//const SMALLEST_CHUNK_LIMIT = 256 * 4;
const STREAM_CHUNK_UPPER_LIMIT = 1024 * 1024;
const SMALLEST_CHUNK_LIMIT = 1024 * 4;
function parseRange(header: string): [number, number] {
if(!header) return [0, 0];
@ -462,3 +469,9 @@ function alignOffset(offset: number, base = SMALLEST_CHUNK_LIMIT) { @@ -462,3 +469,9 @@ function alignOffset(offset: number, base = SMALLEST_CHUNK_LIMIT) {
function alignLimit(limit: number) {
return 2 ** Math.ceil(Math.log(limit) / Math.log(2));
}
// @ts-ignore
if(process.env.NODE_ENV != 'production') {
(ctx as any).onMessage = onMessage;
(ctx as any).onFetch = onFetch;
}

10
src/scss/partials/_chatBubble.scss

@ -878,7 +878,7 @@ $bubble-margin: .25rem; @@ -878,7 +878,7 @@ $bubble-margin: .25rem;
padding: 0;
display: flex;
align-items: center;
width: auto;
width: auto !important;
.inner {
margin-bottom: 0;
@ -910,7 +910,7 @@ $bubble-margin: .25rem; @@ -910,7 +910,7 @@ $bubble-margin: .25rem;
i {
font-size: 1.15rem;
margin-right: .4rem;
margin-left: .1rem;
/* margin-left: .1rem; */
}
i.edited {
@ -960,7 +960,8 @@ $bubble-margin: .25rem; @@ -960,7 +960,8 @@ $bubble-margin: .25rem;
&.emoji-big, &.sticker {
.time {
width: 81px !important;
/* width: 81px !important; */
min-width: unset;
}
}
}
@ -1167,7 +1168,8 @@ $bubble-margin: .25rem; @@ -1167,7 +1168,8 @@ $bubble-margin: .25rem;
.time {
color: #a3adb6;
width: 36px;
/* width: 36px; */
padding-left: 36px;
.inner {
padding: 0 7px 0 5px;

29
src/scss/partials/_emojiDropdown.scss

@ -322,33 +322,4 @@ @@ -322,33 +322,4 @@
}
}
}
#content-gifs {
.gifs-masonry {
display: flex;
flex-wrap: wrap;
> .gif {
flex: 1 0 auto;
max-width: 100%;
height: 100px;
margin: 2.5px;
cursor: pointer;
//background: #000;
position: relative;
video, img {
object-fit: cover;
width: 100%;
height: 100%;
}
img {
position: absolute;
left: 0;
top: 0;
}
}
}
}
}

26
src/scss/partials/_gifsMasonry.scss

@ -0,0 +1,26 @@ @@ -0,0 +1,26 @@
.gifs-masonry {
display: flex;
flex-wrap: wrap;
> .gif {
flex: 1 0 auto;
max-width: 100%;
height: 100px;
margin: 2.5px;
cursor: pointer;
//background: #000;
position: relative;
video, img {
object-fit: cover;
width: 100%;
height: 100%;
}
img {
position: absolute;
left: 0;
top: 0;
}
}
}

1
src/scss/style.scss

@ -48,6 +48,7 @@ $large-screen: 1680px; @@ -48,6 +48,7 @@ $large-screen: 1680px;
@import "partials/scrollable";
@import "partials/slider";
@import "partials/selector";
@import "partials/gifsMasonry";
@import "partials/popups/popup";
@import "partials/popups/editAvatar";

3
src/types.d.ts vendored

@ -9,6 +9,7 @@ export type MTDocument = { @@ -9,6 +9,7 @@ export type MTDocument = {
mime_type: string,
size: number,
thumbs: MTPhotoSize[],
video_thumbs?: MTVideoSize[],
dc_id: number,
attributes: any[],
@ -46,6 +47,8 @@ export type MTPhotoSize = { @@ -46,6 +47,8 @@ export type MTPhotoSize = {
url?: string
};
export type MTVideoSize = Omit<MTPhotoSize, '_'> & {_: 'videoSize'};
export type InvokeApiOptions = Partial<{
dcID: number,
timeout: number,

Loading…
Cancel
Save