Browse Source

Multiple pinned messages in topbar & animations

Layer 121
master
Eduard Kuzmenko 4 years ago
parent
commit
bc71749b46
  1. 6
      src/components/chat/contextMenu.ts
  2. 5
      src/components/chat/messageRender.ts
  3. 388
      src/components/chat/pinnedMessage.ts
  4. 177
      src/components/chat/pinnedMessageBorder.ts
  5. 145
      src/components/chat/replyContainer.ts
  6. 4
      src/components/horizontalMenu.ts
  7. 20
      src/components/popupUnpinMessage.ts
  8. 34
      src/components/scrollable.ts
  9. 6
      src/components/sidebarLeft/index.ts
  10. 2
      src/components/singleTransition.ts
  11. 67
      src/components/slider.ts
  12. 78
      src/components/transition.ts
  13. 2
      src/components/wrappers.ts
  14. 3082
      src/layer.d.ts
  15. 105
      src/lib/appManagers/appImManager.ts
  16. 153
      src/lib/appManagers/appMessagesManager.ts
  17. 2
      src/lib/mtproto/schema.ts
  18. 2
      src/lib/rootScope.ts
  19. 2
      src/scripts/in/schema.json
  20. 2
      src/scripts/out/schema.json
  21. 263
      src/scss/partials/_chat.scss
  22. 383
      src/scss/partials/_chatPinned.scss
  23. 2
      src/scss/partials/_leftSidebar.scss
  24. 74
      src/scss/style.scss

6
src/components/chat/contextMenu.ts

@ -165,13 +165,13 @@ export default class ChatContextMenu { @@ -165,13 +165,13 @@ export default class ChatContextMenu {
const message = appMessagesManager.getMessage(this.msgID);
// for new layer
// return this.msgID > 0 && message._ != 'messageService' && appImManager.pinnedMsgID != this.msgID && (this.peerID > 0 || appChatsManager.hasRights(-this.peerID, 'pin'));
return this.msgID > 0 && message._ != 'messageService' && appImManager.pinnedMsgID != this.msgID && (this.peerID == rootScope.myID || (this.peerID < 0 && appChatsManager.hasRights(-this.peerID, 'pin')));
return this.msgID > 0 && message._ != 'messageService' && /* appImManager.pinnedMsgID != this.msgID && */ (this.peerID == rootScope.myID || (this.peerID < 0 && appChatsManager.hasRights(-this.peerID, 'pin')));
}
}, {
icon: 'unpin',
text: 'Unpin',
onClick: this.onUnpinClick,
verify: () => appImManager.pinnedMsgID == this.msgID && appPeersManager.canPinMessage(this.peerID)
verify: () => /* appImManager.pinnedMsgID == this.msgID && */ appPeersManager.canPinMessage(this.peerID)
}, {
icon: 'revote',
text: 'Revote',
@ -269,7 +269,7 @@ export default class ChatContextMenu { @@ -269,7 +269,7 @@ export default class ChatContextMenu {
};
private onUnpinClick = () => {
new PopupPinMessage(rootScope.selectedPeerID, 0);
new PopupPinMessage(rootScope.selectedPeerID, this.msgID, true);
};
private onRetractVote = () => {

5
src/components/chat/messageRender.ts

@ -39,6 +39,11 @@ export namespace MessageRender { @@ -39,6 +39,11 @@ export namespace MessageRender {
time = '<i class="edited">edited</i> ' + time;
}
if(message.pFlags.pinned) {
bubble.classList.add('is-pinned');
time = '<i class="tgico tgico-pinnedchat"></i>' + time;
}
const title = getFullDate(date)
+ (message.edit_date ? `\nEdited: ${getFullDate(new Date(message.edit_date * 1000))}` : '')
+ (message.fwd_from ? `\nOriginal: ${getFullDate(new Date(message.fwd_from.date * 1000))}` : '');

388
src/components/chat/pinnedMessage.ts

@ -0,0 +1,388 @@ @@ -0,0 +1,388 @@
import type { AppImManager } from "../../lib/appManagers/appImManager";
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
import { ScreenSize } from "../../helpers/mediaSizes";
import appPeersManager from "../../lib/appManagers/appPeersManager";
import PopupPinMessage from "../popupUnpinMessage";
import PinnedContainer from "./pinnedContainer";
import PinnedMessageBorder from "./pinnedMessageBorder";
import ReplyContainer, { wrapReplyDivAndCaption } from "./replyContainer";
import rootScope from "../../lib/rootScope";
import { findUpClassName } from "../../helpers/dom";
class AnimatedSuper {
static DURATION = 200;
static BASE_CLASS = 'animated-super';
container: HTMLDivElement;
rows: {[index: string]: {element: HTMLElement, timeout?: number, new?: true}} = {};
clearTimeout: number;
constructor() {
this.container = document.createElement('div');
this.container.className = AnimatedSuper.BASE_CLASS;
}
public getRow(index: number, animateFirst = false) {
if(this.rows[index]) return this.rows[index].element;
const row = document.createElement('div');
const isFirst = !Object.keys(this.rows).length && !animateFirst;
row.className = AnimatedSuper.BASE_CLASS + '-row' + (isFirst ? '' : ' is-hiding hide');
this.rows[index] = {element: row, new: true};
this.container.append(row);
return row;
}
public clearRow(index: number) {
if(!this.rows[index]) return;
this.rows[index].element.remove();
delete this.rows[index];
}
public clearRows(currentIndex?: number) {
if(this.clearTimeout) clearTimeout(this.clearTimeout);
this.clearTimeout = window.setTimeout(() => {
for(const i in this.rows) {
if(+i === currentIndex) continue;
this.clearRow(+i);
}
}, AnimatedSuper.DURATION);
}
public /* async */ animate(index: number, previousIndex: number, fromTop = index > previousIndex, ignorePrevious = false) {
if(index == previousIndex) {
this.clearRows(index);
return;
}
//const fromTop = index > previousIndex;
const row = this.rows[index];
const previousRow = this.rows[previousIndex];
//const height = this.container.getBoundingClientRect().height;
if(!previousRow && !ignorePrevious) {
if(row.new) {
row.element.classList.remove('hide');
}
return;
}
const sides = ['from-top', 'from-bottom'];
if(!fromTop) sides.reverse();
row.element.classList.add(sides[0]);
row.element.classList.remove(sides[1]);
if(previousRow) {
previousRow.element.classList.add(sides[1]);
previousRow.element.classList.remove(sides[0]);
}
if(row.new) {
//await new Promise((resolve) => window.requestAnimationFrame(resolve));
row.element.classList.remove('hide');
void row.element.offsetLeft; // reflow
delete row.new;
//await new Promise((resolve) => window.requestAnimationFrame(resolve));
}
row.element.classList.toggle('is-hiding', false);
previousRow && previousRow.element.classList.toggle('is-hiding', true);
//SetTransition(row.element, 'is-hiding', false, AnimatedSuper.DURATION);
//previousRow && SetTransition(previousRow.element, 'is-hiding', true, AnimatedSuper.DURATION);
this.clearRows(index);
}
}
class AnimatedCounter {
static BASE_CLASS = 'animated-counter';
container: HTMLElement;
decimals: {
container: HTMLElement,
placeholder: HTMLElement,
animatedSuper: AnimatedSuper
}[] = [];
previousNumber = 0;
clearTimeout: number;
constructor(private reverse = false) {
this.container = document.createElement('div');
this.container.className = AnimatedCounter.BASE_CLASS;
}
getDecimal(index: number) {
if(this.decimals[index]) return this.decimals[index];
const item = document.createElement('div');
item.className = AnimatedCounter.BASE_CLASS + '-decimal';
const placeholder = document.createElement('div');
placeholder.className = AnimatedCounter.BASE_CLASS + '-decimal-placeholder';
const animatedSuper = new AnimatedSuper();
animatedSuper.container.className = AnimatedCounter.BASE_CLASS + '-decimal-wrapper';
item.append(placeholder, animatedSuper.container);
this.container.append(item);
return this.decimals[index] = {container: item, placeholder, animatedSuper};
}
clear(number: number) {
if(this.clearTimeout) clearTimeout(this.clearTimeout);
const decimals = ('' + number).length;
if(decimals >= this.decimals.length) {
return;
}
this.clearTimeout = window.setTimeout(() => {
const byDecimal = this.decimals.splice(decimals, this.decimals.length - decimals);
byDecimal.forEach((decimal) => {
decimal.container.remove();
});
}, AnimatedSuper.DURATION);
}
/* prepareNumber(number: number) {
const decimals = ('' + number).length;
if(this.decimals.length < decimals) {
for(let i = this.decimals.length; i < decimals; ++i) {
this.getDecimal(i);
}
}
} */
hideLeft(number: number) {
const decimals = ('' + number).length;
const byDecimal = this.decimals.slice(decimals);//this.decimals.splice(deleteCount, this.decimals.length - deleteCount);
const EMPTY_INDEX = 0;
byDecimal.forEach((decimal) => {
const row = decimal.animatedSuper.getRow(EMPTY_INDEX, true);
decimal.animatedSuper.animate(EMPTY_INDEX, this.previousNumber, this.reverse ? number < this.previousNumber : number > this.previousNumber, true);
//decimal.container.remove();
//decimal.animatedSuper.clearRows();
});
this.clear(number);
}
setCount(number: number) {
//this.prepareNumber(number);
const byDecimal = Array.from('' + number).map(n => +n);
byDecimal.forEach((decimalNumber, idx) => {
const decimal = this.getDecimal(idx);
const row = decimal.animatedSuper.getRow(number, true);
row.innerText = decimal.placeholder.innerText = '' + decimalNumber;
decimal.animatedSuper.animate(number, this.previousNumber, this.reverse ? number < this.previousNumber : number > this.previousNumber, true);
});
/* const sides = ['from-top', 'from-bottom'];
if(this.reverse) {
sides.reverse();
}
const isHigher = number > this.previousNumber;
if(!isHigher) {
sides.reverse();
}
this.container.classList.add(sides[0]);
this.container.classList.remove(sides[1]); */
this.hideLeft(number);
//this.clear(number);
this.previousNumber = number;
}
}
export default class PinnedMessage {
public pinnedMessageContainer: PinnedContainer;
public pinnedMessageBorder: PinnedMessageBorder;
public pinnedIndex = 0;
public wasPinnedIndex = 0;
public locked = false;
public waitForScrollBottom = false;
public animatedSubtitle: AnimatedSuper;
public animatedMedia: AnimatedSuper;
public animatedCounter: AnimatedCounter;
constructor(private appImManager: AppImManager, private appMessagesManager: AppMessagesManager) {
this.pinnedMessageContainer = new PinnedContainer('message', new ReplyContainer('pinned-message'), () => {
if(appPeersManager.canPinMessage(this.appImManager.peerID)) {
new PopupPinMessage(this.appImManager.peerID, 0);
return Promise.resolve(false);
}
});
this.pinnedMessageBorder = new PinnedMessageBorder();
this.pinnedMessageContainer.divAndCaption.border.replaceWith(this.pinnedMessageBorder.render(1, 0));
this.appImManager.btnJoin.parentElement.insertBefore(this.pinnedMessageContainer.divAndCaption.container, this.appImManager.btnJoin);
this.animatedSubtitle = new AnimatedSuper();
this.pinnedMessageContainer.divAndCaption.subtitle.append(this.animatedSubtitle.container);
this.animatedMedia = new AnimatedSuper();
this.animatedMedia.container.classList.add('pinned-message-media-container');
this.pinnedMessageContainer.divAndCaption.content.prepend(this.animatedMedia.container);
this.animatedCounter = new AnimatedCounter(true);
this.pinnedMessageContainer.divAndCaption.title.innerHTML = 'Pinned Message ';
this.pinnedMessageContainer.divAndCaption.title.append(this.animatedCounter.container);
rootScope.on('peer_pinned_messages', (e) => {
const peerID = e.detail;
if(peerID == this.appImManager.peerID) {
this.setPinnedMessage();
}
});
}
public setCorrectIndex(lastScrollDirection?: number) {
if(this.locked) {
return;
}/* else if(this.waitForScrollBottom) {
if(lastScrollDirection === 1) {
this.waitForScrollBottom = false;
} else {
return;
}
} */
///const perf = performance.now();
const rect = this.appImManager.scrollable.container.getBoundingClientRect();
const x = Math.ceil(rect.left + ((rect.right - rect.left) / 2) + 1);
const y = Math.floor(rect.top + rect.height - 1);
let el: HTMLElement = document.elementFromPoint(x, y) as any;
//this.appImManager.log('[PM]: setCorrectIndex: get last element perf:', performance.now() - perf, el, x, y);
el = findUpClassName(el, 'bubble');
if(el && el.dataset.mid !== undefined) {
const mid = +el.dataset.mid;
this.appMessagesManager.getPinnedMessages(this.appImManager.peerID).then(mids => {
let currentIndex = mids.findIndex(_mid => _mid <= mid);
if(currentIndex === -1) {
currentIndex = mids.length ? mids.length - 1 : 0;
}
this.appImManager.log('pinned currentIndex', currentIndex);
const changed = this.pinnedIndex != currentIndex;
if(changed) {
if(this.waitForScrollBottom) {
if(lastScrollDirection === 1) { // если проскроллил вниз - разблокировать
this.waitForScrollBottom = false;
} else if(this.pinnedIndex > currentIndex) { // если не скроллил вниз и пытается поставить нижний пиннед - выйти
return;
}
}
this.pinnedIndex = currentIndex;
this.setPinnedMessage();
}
});
}
}
public async followPinnedMessage(mid: number) {
const message = this.appMessagesManager.getMessage(mid);
if(message && !message.deleted) {
this.locked = true;
try {
const mids = await this.appMessagesManager.getPinnedMessages(message.peerID);
const index = mids.indexOf(mid);
this.pinnedIndex = index >= (mids.length - 1) ? 0 : index + 1;
this.setPinnedMessage();
const setPeerPromise = this.appImManager.setPeer(message.peerID, mid);
if(setPeerPromise instanceof Promise) {
await setPeerPromise;
}
await this.appImManager.scrollable.scrollLockedPromise;
} catch(err) {
this.appImManager.log.error('[PM]: followPinnedMessage error:', err);
}
// подождём, пока скролл остановится
setTimeout(() => {
this.locked = false;
this.waitForScrollBottom = true;
}, 50);
}
}
public onChangeScreen(from: ScreenSize, to: ScreenSize) {
this.pinnedMessageContainer.divAndCaption.container.classList.toggle('is-floating', to == ScreenSize.mobile
/* || (!this.chatAudio.divAndCaption.container.classList.contains('hide') && to == ScreenSize.medium) */);
}
public setPinnedMessage() {
/////this.log('setting pinned message', message);
//return;
const promise: Promise<any> = this.appImManager.setPeerPromise || this.appImManager.messagesQueuePromise || Promise.resolve();
Promise.all([
this.appMessagesManager.getPinnedMessages(this.appImManager.peerID),
promise
]).then(([mids]) => {
//const mids = results[0];
if(mids.length) {
const pinnedIndex = this.pinnedIndex >= mids.length ? mids.length - 1 : this.pinnedIndex;
const message = this.appMessagesManager.getMessage(mids[pinnedIndex]);
//this.animatedCounter.prepareNumber(mids.length);
//setTimeout(() => {
const isLast = pinnedIndex === 0;
this.animatedCounter.container.classList.toggle('is-last', isLast);
//SetTransition(this.animatedCounter.container, 'is-last', isLast, AnimatedSuper.DURATION);
if(!isLast) {
this.animatedCounter.setCount(mids.length - pinnedIndex);
}
//}, 100);
//this.pinnedMessageContainer.fill(undefined, message.message, message);
this.pinnedMessageContainer.toggle(false);
const fromTop = pinnedIndex > this.wasPinnedIndex;
this.appImManager.log('[PM]: setPinnedMessage: fromTop', fromTop, pinnedIndex, this.wasPinnedIndex);
const writeTo = this.animatedSubtitle.getRow(pinnedIndex);
const writeMediaTo = this.animatedMedia.getRow(pinnedIndex);
writeMediaTo.classList.add('pinned-message-media');
const isMediaSet = wrapReplyDivAndCaption({
title: undefined,
titleEl: null,
subtitle: message.message,
subtitleEl: writeTo,
message,
mediaEl: writeMediaTo
});
this.pinnedMessageContainer.divAndCaption.container.classList.toggle('is-media', isMediaSet);
if(this.wasPinnedIndex != this.pinnedIndex) {
this.animatedSubtitle.animate(pinnedIndex, this.wasPinnedIndex);
if(isMediaSet) {
this.animatedMedia.animate(pinnedIndex, this.wasPinnedIndex);
} else {
this.animatedMedia.clearRows();
}
}
this.pinnedMessageBorder.render(mids.length, mids.length - pinnedIndex - 1);
this.wasPinnedIndex = pinnedIndex;
this.pinnedMessageContainer.divAndCaption.container.dataset.mid = '' + message.mid;
} else {
this.pinnedMessageContainer.toggle(true);
this.wasPinnedIndex = 0;
}
});
}
}

177
src/components/chat/pinnedMessageBorder.ts

@ -0,0 +1,177 @@ @@ -0,0 +1,177 @@
// https://github.com/evgeny-nadymov/telegram-react/blob/master/src/Components/ColumnMiddle/PinnedMessageBorder.js
export default class PinnedMessageBorder {
private border: HTMLElement;
private wrapper: HTMLElement;
private svg: SVGSVGElement;
private defs: SVGDefsElement;
private clipPath: SVGClipPathElement;
private path: SVGPathElement;
private mark: HTMLElement;
private count: number;
private index: number;
private drawRect = (x: number, y: number, width: number, height: number, radius: number) => {
return `M${x},${y + radius}a${radius},${radius},0,0,1,${width},0v${height - 2 * radius}a${radius},${radius},0,0,1,${-width},0Z`;
};
private getClipPath = (id: string, barHeight: number, count: number) => {
const radius = 1;
let d = '';
if(count === 3) {
d = this.drawRect(0, 0, 2, barHeight, radius)
+ this.drawRect(0, 11, 2, barHeight + 1, radius)
+ this.drawRect(0, 23, 2, barHeight, radius);
} else {
for(let i = 0; i < count; i++) {
d += this.drawRect(0, (barHeight + 2) * i, 2, barHeight, radius);
}
}
if(!this.clipPath) {
this.clipPath = document.createElementNS('http://www.w3.org/2000/svg', 'clipPath');
this.path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
this.clipPath.append(this.path);
}
this.clipPath.id = id;
this.path.setAttributeNS(null, 'd', d);
return this.clipPath;
};
private getBarHeight = (count: number, index: number) => {
let barHeight = 32;
if(count === 1) {
barHeight = 32;
} else if(count === 2) {
barHeight = 15;
} else if(count === 3) {
barHeight = 9;
} else if(count === 4) {
barHeight = 7;
} else if(count > 3) {
barHeight = 7;
}
return barHeight;
};
private getMarkHeight = (count: number, index: number) => {
let barHeight = 32;
if(count === 1) {
barHeight = 32;
} else if(count === 2) {
barHeight = 15;
} else if(count === 3) {
barHeight = index === 1 ? 10 : 9;
} else if(count === 4) {
barHeight = 7;
} else if(count > 3) {
barHeight = 7;
}
return barHeight;
};
private getMarkTranslateY = (index: number, barHeight: number, count: number) => {
if(count === 1) {
return 0;
} else if(count === 2) {
return index === 0 ? 0 : barHeight + 2;
}
if(count === 3) {
if(index === 0) {
return 0;
} else if (index === 1) {
return 11;
}
return 23;
} else {
return (barHeight + 2) * index;
}
};
private getTrackTranslateY = (index: number, count: number, barHeight: number, trackHeight: number) => {
if(count <= 4) {
return 0;
}
if(index <= 1) {
return 0;
} else if(index >= count - 2) {
return trackHeight - 32;
}
return (barHeight + 4) / 2 + (index - 2) * (barHeight + 2);
};
private getTrackHeight = (count: number, barHeight: number) => {
return count <= 3 ? 32 : barHeight * count + 2 * (count - 1);
};
public render(count: number, index: number) {
if(!this.border) {
this.border = document.createElement('div');
this.border.classList.add('pinned-message-border');
this.wrapper = document.createElement('div');
this.border.append(this.wrapper);
}
if(count === 1) {
if(this.count !== count) {
this.wrapper.className = 'pinned-message-border-wrapper-1';
this.border.classList.remove('pinned-message-border-mask');
this.wrapper.innerHTML = '';
}
return this.border;
}
const barHeight = this.getBarHeight(count, index);
const markHeight = this.getMarkHeight(count, index);
const trackHeight = this.getTrackHeight(count, barHeight);
const clipPathId = `clipPath_${count}`;
const clipPath = this.getClipPath(clipPathId, barHeight, count);
const markTranslateY = this.getMarkTranslateY(index, barHeight, count);
const trackTranslateY = this.getTrackTranslateY(index, count, barHeight, trackHeight);
this.border.classList.toggle('pinned-message-border-mask', count > 4);
this.wrapper.className = 'pinned-message-border-wrapper';
this.wrapper.style.cssText = `clip-path: url(#${clipPathId}); width: 2px; height: ${trackHeight}px; transform: translateY(-${trackTranslateY}px);`;
if(!this.svg) {
this.svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
this.svg.setAttributeNS(null, 'height', '0');
this.svg.setAttributeNS(null, 'width', '0');
this.defs = document.createElementNS('http://www.w3.org/2000/svg', 'defs');
this.defs.append(clipPath);
this.svg.append(this.defs);
this.mark = document.createElement('div');
this.mark.classList.add('pinned-message-border-mark');
}
if(!this.svg.parentElement) {
this.wrapper.append(this.svg, this.mark);
}
this.mark.style.cssText = `height: ${markHeight}px; transform: translateY(${markTranslateY}px);`;
this.count = count;
this.index = index;
return this.border;
}
}

145
src/components/chat/replyContainer.ts

@ -5,82 +5,103 @@ import DivAndCaption from "../divAndCaption"; @@ -5,82 +5,103 @@ import DivAndCaption from "../divAndCaption";
import { renderImageFromUrl } from "../misc";
import { wrapSticker } from "../wrappers";
export default class ReplyContainer extends DivAndCaption<(title: string, subtitle: string, message?: any) => void> {
private mediaEl: HTMLElement;
export function wrapReplyDivAndCaption(options: {
title: string,
titleEl: HTMLElement,
subtitle: string,
subtitleEl: HTMLElement,
message: any,
mediaEl: HTMLElement
}) {
let {title, titleEl, subtitle, subtitleEl, mediaEl, message} = options;
if(title !== undefined) {
if(title.length > 150) {
title = title.substr(0, 140) + '...';
}
constructor(protected className: string) {
super(className, (title: string, subtitle: string = '', message?: any) => {
if(title.length > 150) {
title = title.substr(0, 140) + '...';
}
title = title ? RichTextProcessor.wrapEmojiText(title) : '';
titleEl.innerHTML = title;
}
if(subtitle.length > 150) {
subtitle = subtitle.substr(0, 140) + '...';
}
if(subtitle.length > 150) {
subtitle = subtitle.substr(0, 140) + '...';
}
title = title ? RichTextProcessor.wrapEmojiText(title) : '';
const media = message && message.media;
let setMedia = false;
if(media && mediaEl) {
subtitle = message.rReply;
if(this.mediaEl) {
this.mediaEl.remove();
this.container.classList.remove('is-media');
}
//console.log('wrap reply', media);
const media = message && message.media;
if(media) {
subtitle = message.rReply;
if(media.photo || (media.document && ['video', 'sticker', 'gif'].indexOf(media.document.type) !== -1)) {
if(media.document?.type == 'sticker') {
setMedia = true;
wrapSticker({
doc: media.document,
div: mediaEl,
lazyLoadQueue: appImManager.lazyLoadQueue,
group: CHAT_ANIMATION_GROUP,
onlyThumb: media.document.sticker == 2,
width: 32,
height: 32
});
} else {
const photo = media.photo || media.document;
//console.log('wrap reply', media);
const cacheContext = appPhotosManager.getCacheContext(photo);
if(!cacheContext.downloaded) {
const sizes = photo.sizes || photo.thumbs;
if(sizes && sizes[0].bytes) {
setMedia = true;
renderImageFromUrl(mediaEl, appPhotosManager.getPreviewURLFromThumb(sizes[0]));
}
}
if(media.photo || (media.document && ['video', 'sticker', 'gif'].indexOf(media.document.type) !== -1)) {
let good = false;
const replyMedia = document.createElement('div');
replyMedia.classList.add(this.className + '-media');
const size = appPhotosManager.choosePhotoSize(photo, 32, 32/* mediaSizes.active.regular.width, mediaSizes.active.regular.height */);
if(size._ != 'photoSizeEmpty') {
setMedia = true;
appPhotosManager.preloadPhoto(photo, size)
.then(() => {
renderImageFromUrl(mediaEl, photo._ == 'photo' ? photo.url : appPhotosManager.getDocumentCachedThumb(photo.id).url);
});
}
}
}
} else {
subtitle = subtitle ? RichTextProcessor.wrapEmojiText(subtitle) : '';
}
if(media.document?.type == 'sticker') {
good = true;
wrapSticker({
doc: media.document,
div: replyMedia,
lazyLoadQueue: appImManager.lazyLoadQueue,
group: CHAT_ANIMATION_GROUP,
onlyThumb: media.document.sticker == 2,
width: 32,
height: 32
});
} else {
const photo = media.photo || media.document;
subtitleEl.innerHTML = subtitle;
return setMedia;
}
const cacheContext = appPhotosManager.getCacheContext(photo);
export default class ReplyContainer extends DivAndCaption<(title: string, subtitle: string, message?: any) => void> {
private mediaEl: HTMLElement;
if(!cacheContext.downloaded) {
const sizes = photo.sizes || photo.thumbs;
if(sizes && sizes[0].bytes) {
good = true;
renderImageFromUrl(replyMedia, appPhotosManager.getPreviewURLFromThumb(sizes[0]));
}
}
constructor(protected className: string) {
super(className, (title: string, subtitle: string = '', message?: any) => {
if(!this.mediaEl) {
this.mediaEl = document.createElement('div');
this.mediaEl.classList.add(this.className + '-media');
}
const size = appPhotosManager.choosePhotoSize(photo, 32, 32/* mediaSizes.active.regular.width, mediaSizes.active.regular.height */);
if(size._ != 'photoSizeEmpty') {
good = true;
appPhotosManager.preloadPhoto(photo, size)
.then(() => {
renderImageFromUrl(replyMedia, photo._ == 'photo' ? photo.url : appPhotosManager.getDocumentCachedThumb(photo.id).url);
});
}
}
const isMediaSet = wrapReplyDivAndCaption({
title,
titleEl: this.title,
subtitle,
subtitleEl: this.subtitle,
mediaEl: this.mediaEl,
message
});
if(good) {
this.content.prepend(this.mediaEl = replyMedia);
this.container.classList.add('is-media');
}
}
this.container.classList.toggle('is-media', isMediaSet);
if(isMediaSet) {
this.content.prepend(this.mediaEl);
} else {
subtitle = subtitle ? RichTextProcessor.wrapEmojiText(subtitle) : '';
this.mediaEl.remove();
}
this.title.innerHTML = title;
this.subtitle.innerHTML = subtitle;
});
}
}

4
src/components/horizontalMenu.ts

@ -1,8 +1,8 @@ @@ -1,8 +1,8 @@
import { findUpTag, whichChild } from "../helpers/dom";
import Transition from "./transition";
import { TransitionSlider } from "./transition";
export function horizontalMenu(tabs: HTMLElement, content: HTMLElement, onClick?: (id: number, tabContent: HTMLDivElement) => void, onTransitionEnd?: () => void, transitionTime = 250) {
const selectTab = Transition(content, tabs || content.dataset.slider == 'tabs' ? 'tabs' : 'navigation', transitionTime, onTransitionEnd);
const selectTab = TransitionSlider(content, tabs || content.dataset.slider == 'tabs' ? 'tabs' : 'navigation', transitionTime, onTransitionEnd);
if(tabs) {
const useStripe = !tabs.classList.contains('no-stripe');

20
src/components/popupUnpinMessage.ts

@ -3,18 +3,11 @@ import { PopupButton } from "./popup"; @@ -3,18 +3,11 @@ import { PopupButton } from "./popup";
import PopupPeer from "./popupPeer";
export default class PopupPinMessage {
constructor(peerID: number, mid: number) {
constructor(peerID: number, mid: number, unpin?: true) {
let title: string, description: string, buttons: PopupButton[] = [];
const callback = () => appMessagesManager.updatePinnedMessage(peerID, mid);
if(mid) {
title = 'Pin Message?';
description = 'Would you like to pin this message?';
buttons.push({
text: 'PIN',
callback
});
} else {
const callback = () => appMessagesManager.updatePinnedMessage(peerID, mid, unpin);
if(unpin) {
title = `Unpin Message?`;
description = 'Would you like to unpin this message?';
buttons.push({
@ -22,6 +15,13 @@ export default class PopupPinMessage { @@ -22,6 +15,13 @@ export default class PopupPinMessage {
isDanger: true,
callback
});
} else {
title = 'Pin Message?';
description = 'Would you like to pin this message?';
buttons.push({
text: 'PIN',
callback
});
}
buttons.push({

34
src/components/scrollable.ts

@ -124,12 +124,14 @@ export type SliceSidesContainer = {[k in SliceSides]: boolean}; @@ -124,12 +124,14 @@ export type SliceSidesContainer = {[k in SliceSides]: boolean};
export default class Scrollable extends ScrollableBase {
public splitUp: HTMLElement;
public onAdditionalScroll: () => void = null;
public onScrolledTop: () => void = null;
public onScrolledBottom: () => void = null;
public onScrollMeasure: number = null;
private lastScrollTop: number = 0;
public lastScrollTop: number = 0;
public lastScrollDirection: number = 0;
public loadedAll: SliceSidesContainer = {top: true, bottom: false};
@ -150,37 +152,43 @@ export default class Scrollable extends ScrollableBase { @@ -150,37 +152,43 @@ export default class Scrollable extends ScrollableBase {
//this.log('onScroll call', this.onScrollMeasure);
//}
if(this.onScrollMeasure || ((this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) && !this.splitUp)) return;
if(this.onScrollMeasure || ((this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) && !this.splitUp && !this.onAdditionalScroll)) return;
this.onScrollMeasure = window.requestAnimationFrame(() => {
this.checkForTriggers();
this.onScrollMeasure = 0;
this.lastScrollTop = this.scrollTop;
const scrollTop = this.container.scrollTop;
this.lastScrollDirection = this.lastScrollTop == scrollTop ? 0 : (this.lastScrollTop < scrollTop ? 1 : -1); // * 1 - bottom, -1 - top
this.lastScrollTop = scrollTop;
if(this.onAdditionalScroll) {
this.onAdditionalScroll();
}
if(this.checkForTriggers) {
this.checkForTriggers();
}
});
};
public checkForTriggers = () => {
if(this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) return;
const container = this.container;
const scrollHeight = container.scrollHeight;
const scrollHeight = this.container.scrollHeight;
if(!scrollHeight) { // незачем вызывать триггеры если блок пустой или не виден
return;
}
const {clientHeight, scrollTop} = container;
const clientHeight = this.container.clientHeight;
const maxScrollTop = scrollHeight - clientHeight;
// 1 - bottom, -1 - top
const direction = this.lastScrollTop == scrollTop ? 0 : (this.lastScrollTop < scrollTop ? 1 : -1);
const scrollTop = this.lastScrollTop;
//this.log('checkForTriggers:', scrollTop, maxScrollTop);
if(this.onScrolledTop && scrollTop <= this.onScrollOffset && direction <= 0/* && direction === -1 */) {
if(this.onScrolledTop && scrollTop <= this.onScrollOffset && this.lastScrollDirection <= 0/* && direction === -1 */) {
this.onScrolledTop();
}
if(this.onScrolledBottom && (maxScrollTop - scrollTop) <= this.onScrollOffset && direction >= 0/* && direction === 1 */) {
if(this.onScrolledBottom && (maxScrollTop - scrollTop) <= this.onScrollOffset && this.lastScrollDirection >= 0/* && direction === 1 */) {
this.onScrolledBottom();
}
};

6
src/components/sidebarLeft/index.ts

@ -11,11 +11,11 @@ import rootScope from "../../lib/rootScope"; @@ -11,11 +11,11 @@ import rootScope from "../../lib/rootScope";
import { CLICK_EVENT_NAME, findUpClassName, findUpTag } from "../../helpers/dom";
import AppSearch, { SearchGroup } from "../appSearch";
import "../avatar";
import { parseMenuButtonsTo, putPreloader } from "../misc";
import { parseMenuButtonsTo } from "../misc";
import { ScrollableX } from "../scrollable";
import SearchInput from "../searchInput";
import SidebarSlider from "../slider";
import Transition from "../transition";
import { TransitionSlider } from "../transition";
import AppAddMembersTab from "./tabs/addMembers";
import AppArchivedTab from "./tabs/archivedTab";
import AppChatFoldersTab from "./tabs/chatFolders";
@ -226,7 +226,7 @@ export class AppSidebarLeft extends SidebarSlider { @@ -226,7 +226,7 @@ export class AppSidebarLeft extends SidebarSlider {
let hideNewBtnMenuTimeout: number;
//const transition = Transition.bind(null, this.searchContainer.parentElement, 150);
const transition = Transition(this.searchContainer.parentElement, 'zoom-fade', 150, (id) => {
const transition = TransitionSlider(this.searchContainer.parentElement, 'zoom-fade', 150, (id) => {
if(hideNewBtnMenuTimeout) clearTimeout(hideNewBtnMenuTimeout);
if(id == 0) {

2
src/components/singleTransition.ts

@ -4,7 +4,7 @@ const SetTransition = (element: HTMLElement, className: string, forwards: boolea @@ -4,7 +4,7 @@ const SetTransition = (element: HTMLElement, className: string, forwards: boolea
clearTimeout(+timeout);
}
if(forwards) {
if(forwards && className) {
element.classList.add(className);
}

67
src/components/slider.ts

@ -7,30 +7,61 @@ export interface SliderTab { @@ -7,30 +7,61 @@ export interface SliderTab {
onCloseAfterTimeout?: () => void
}
export class SuperSliderTab implements SliderTab {
public id: number;
public closeBtn: HTMLElement;
// fix incompability
public onOpen: SliderTab['onOpen'];
constructor(protected slider: SidebarSlider, public container: HTMLElement) {
this.closeBtn = this.container.querySelector('.sidebar-close-button');
this.id = this.slider.addTab(this);
}
public close() {
return this.slider.closeTab(this.id);
}
public open() {
return this.slider.selectTab(this);
}
}
const TRANSITION_TIME = 250;
export default class SidebarSlider {
protected _selectTab: (id: number) => void;
public historyTabIDs: number[] = [];
constructor(public sidebarEl: HTMLElement, public tabs: {[id: number]: SliderTab} = {}, canHideFirst = false) {
constructor(public sidebarEl: HTMLElement, public tabs: {[id: number]: SliderTab} = {}, private canHideFirst = false) {
this._selectTab = horizontalMenu(null, this.sidebarEl.querySelector('.sidebar-slider') as HTMLDivElement, null, null, TRANSITION_TIME);
if(!canHideFirst) {
this._selectTab(0);
}
let onCloseBtnClick = () => {
//console.log('sidebar-close-button click:', this.historyTabIDs);
let closingID = this.historyTabIDs.pop(); // pop current
this.onCloseTab(closingID);
this._selectTab(this.historyTabIDs[this.historyTabIDs.length - 1] ?? (canHideFirst ? -1 : 0));
};
Array.from(this.sidebarEl.querySelectorAll('.sidebar-close-button') as any as HTMLElement[]).forEach(el => {
el.addEventListener('click', onCloseBtnClick);
el.addEventListener('click', () => this.closeTab());
});
}
public selectTab(id: number) {
public closeTab = (tabID?: number) => {
if(tabID !== undefined && this.historyTabIDs[this.historyTabIDs.length - 1] != tabID) {
return false;
}
//console.log('sidebar-close-button click:', this.historyTabIDs);
let closingID = this.historyTabIDs.pop(); // pop current
this.onCloseTab(closingID);
this._selectTab(this.historyTabIDs[this.historyTabIDs.length - 1] ?? (this.canHideFirst ? -1 : 0));
return true;
};
public selectTab(id: number | SuperSliderTab): boolean {
if(id instanceof SuperSliderTab) {
id = id.id;
}
if(this.historyTabIDs[this.historyTabIDs.length - 1] == id) {
return false;
}
@ -72,4 +103,22 @@ export default class SidebarSlider { @@ -72,4 +103,22 @@ export default class SidebarSlider {
}
}
}
public addTab(tab: SuperSliderTab) {
let id: number;
if(tab.container.parentElement) {
id = Array.from(this.sidebarEl.children).findIndex(el => el == tab.container);
} else {
id = this.sidebarEl.childElementCount;
this.sidebarEl.append(tab.container);
if(tab.closeBtn) {
tab.closeBtn.addEventListener('click', () => this.closeTab());
}
}
this.tabs[id] = tab;
return id;
}
}

78
src/components/transition.ts

@ -11,6 +11,10 @@ function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, t @@ -11,6 +11,10 @@ function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, t
tabContent.style.transform = '';
tabContent.style.filter = '';
return () => {
prevTabContent.style.transform = prevTabContent.style.filter = '';
};
}
function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
@ -24,16 +28,57 @@ function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight @@ -24,16 +28,57 @@ function slideTabs(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight
void tabContent.offsetWidth; // reflow
tabContent.style.transform = '';
return () => {
prevTabContent.style.transform = '';
};
}
const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fade', transitionTime: number, onTransitionEnd?: (id: number) => void) => {
/* function slideTabsVertical(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
const height = prevTabContent.getBoundingClientRect().height;
const elements = [tabContent, prevTabContent];
if(toRight) elements.reverse();
elements[0].style.transform = `translate3d(0, ${-height}px, 0)`;
elements[1].style.transform = `translate3d(0, ${height}px, 0)`;
tabContent.classList.add('active');
void tabContent.offsetWidth; // reflow
tabContent.style.transform = '';
return () => {
prevTabContent.style.transform = '';
};
} */
export const TransitionSlider = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fade'/* | 'counter' */, transitionTime: number, onTransitionEnd?: (id: number) => void) => {
let animationFunction: TransitionFunction = null;
switch(type) {
case 'tabs':
animationFunction = slideTabs;
break;
case 'navigation':
animationFunction = slideNavigation;
break;
/* case 'counter':
animationFunction = slideTabsVertical;
break; */
/* default:
break; */
}
return Transition(content, animationFunction, transitionTime, onTransitionEnd);
};
type TransitionFunction = (tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) => void | (() => void);
const Transition = (content: HTMLElement, animationFunction: TransitionFunction, transitionTime: number, onTransitionEnd?: (id: number) => void) => {
const hideTimeouts: {[id: number]: number} = {};
//const deferred: (() => void)[] = [];
let transitionEndTimeout: number;
let prevTabContent: HTMLElement = null;
const animationFunction = type == 'zoom-fade' ? null : (type == 'tabs' ? slideTabs : slideNavigation);
function selectTab(id: number, animate = true) {
const self = selectTab;
if(id == self.prevId) return false;
@ -44,7 +89,7 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa @@ -44,7 +89,7 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa
const tabContent = content.children[id] as HTMLElement;
// * means animation isn't needed
if(content.dataset.slider == 'none' || !animate) {
if(/* content.dataset.slider == 'none' || */!animate) {
if(p) {
p.classList.remove('active');
}
@ -67,11 +112,12 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa @@ -67,11 +112,12 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa
const toRight = self.prevId < id;
content.classList.toggle('backwards', !toRight);
let afterTimeout: ReturnType<TransitionFunction>;
if(!tabContent) {
//prevTabContent.classList.remove('active');
} else if(self.prevId != -1) {
if(animationFunction) {
animationFunction(tabContent, prevTabContent, toRight);
afterTimeout = animationFunction(tabContent, prevTabContent, toRight);
}
tabContent.classList.add('to');
@ -83,10 +129,11 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa @@ -83,10 +129,11 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa
if(hideTimeouts.hasOwnProperty(id)) clearTimeout(hideTimeouts[id]);
if(p/* && false */) {
hideTimeouts[_prevId] = window.setTimeout(() => {
p.style.transform = p.style.filter = '';
p.classList.remove('active', 'from');
if(afterTimeout) {
afterTimeout();
}
content.classList.remove('animating', 'backwards');
p.classList.remove('active', 'from');
if(tabContent) {
tabContent.classList.remove('to');
@ -96,13 +143,16 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa @@ -96,13 +143,16 @@ const Transition = (content: HTMLElement, type: 'tabs' | 'navigation' | 'zoom-fa
delete hideTimeouts[_prevId];
}, transitionTime);
if(onTransitionEnd) {
if(transitionEndTimeout) clearTimeout(transitionEndTimeout);
transitionEndTimeout = window.setTimeout(() => {
if(transitionEndTimeout) clearTimeout(transitionEndTimeout);
transitionEndTimeout = window.setTimeout(() => {
if(onTransitionEnd) {
onTransitionEnd(self.prevId);
transitionEndTimeout = 0;
}, transitionTime);
}
}
content.classList.remove('animating', 'backwards');
transitionEndTimeout = 0;
}, transitionTime);
}
self.prevId = id;

2
src/components/wrappers.ts

@ -526,7 +526,7 @@ export function wrapPhoto(photo: MyPhoto | MyDocument, message: any, container: @@ -526,7 +526,7 @@ export function wrapPhoto(photo: MyPhoto | MyDocument, message: any, container:
export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, onlyThumb, emoji, width, height, withThumb, loop}: {
doc: MyDocument,
div: HTMLDivElement,
div: HTMLElement,
middleware?: () => boolean,
lazyLoadQueue?: LazyLoadQueue,
group?: string,

3082
src/layer.d.ts vendored

File diff suppressed because it is too large Load Diff

105
src/lib/appManagers/appImManager.ts

@ -12,7 +12,6 @@ import PinnedContainer from '../../components/chat/pinnedContainer'; @@ -12,7 +12,6 @@ import PinnedContainer from '../../components/chat/pinnedContainer';
import ReplyContainer from '../../components/chat/replyContainer';
import { ChatSearch } from '../../components/chat/search';
import ChatSelection from '../../components/chat/selection';
import CheckboxField from '../../components/checkbox';
import { horizontalMenu } from '../../components/horizontalMenu';
import LazyLoadQueue from '../../components/lazyLoadQueue';
import { formatPhoneNumber, parseMenuButtonsTo } from '../../components/misc';
@ -40,7 +39,7 @@ import apiManager from '../mtproto/mtprotoworker'; @@ -40,7 +39,7 @@ import apiManager from '../mtproto/mtprotoworker';
import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config';
import { RichTextProcessor } from "../richtextprocessor";
import rootScope from '../rootScope';
import { cancelEvent, CLICK_EVENT_NAME, findUpClassName, findUpTag, placeCaretAtEnd, whichChild } from "../../helpers/dom";
import { attachClickEvent, cancelEvent, CLICK_EVENT_NAME, findUpClassName, findUpTag, placeCaretAtEnd, whichChild } from "../../helpers/dom";
import apiUpdatesManager from './apiUpdatesManager';
import appChatsManager, { Channel, Chat } from "./appChatsManager";
import appDialogsManager from "./appDialogsManager";
@ -54,6 +53,7 @@ import appProfileManager from "./appProfileManager"; @@ -54,6 +53,7 @@ import appProfileManager from "./appProfileManager";
import appStickersManager from './appStickersManager';
import appUsersManager from "./appUsersManager";
import { months } from '../../helpers/date';
import PinnedMessage from '../../components/chat/pinnedMessage';
//console.log('appImManager included33!');
@ -108,8 +108,7 @@ export class AppImManager { @@ -108,8 +108,7 @@ export class AppImManager {
public offline = false;
public updateStatusInterval = 0;
public pinnedMsgID = 0;
private pinnedMessageContainer: PinnedContainer = null;
public pinnedMessage: PinnedMessage;
public lazyLoadQueue = new LazyLoadQueue();
@ -136,7 +135,6 @@ export class AppImManager { @@ -136,7 +135,6 @@ export class AppImManager {
public bubbleGroups = new BubbleGroups();
private scrolledDown = true;
private onScrollRAF = 0;
private isScrollingTimeout = 0;
private unreadedObserver: IntersectionObserver = null;
@ -194,20 +192,13 @@ export class AppImManager { @@ -194,20 +192,13 @@ export class AppImManager {
window.addEventListener('resize', () => this.setUtilsWidth(true));
mediaSizes.addListener('changeScreen', (from, to) => {
this.chatAudio.divAndCaption.container.classList.toggle('is-floating', to == ScreenSize.mobile);
this.pinnedMessageContainer.divAndCaption.container.classList.toggle('is-floating', to == ScreenSize.mobile
/* || (!this.chatAudio.divAndCaption.container.classList.contains('hide') && to == ScreenSize.medium) */);
this.pinnedMessage.onChangeScreen(from, to);
this.setUtilsWidth(true);
});
// * fix topbar overflow section end
this.pinnedMessageContainer = new PinnedContainer('message', new ReplyContainer('pinned-message'), () => {
if(appPeersManager.canPinMessage(this.peerID)) {
new PopupPinMessage(this.peerID, 0);
return Promise.resolve(false);
}
});
this.btnJoin.parentElement.insertBefore(this.pinnedMessageContainer.divAndCaption.container, this.btnJoin);
this.pinnedMessage = new PinnedMessage(this, appMessagesManager);
// will call when message is sent (only 1)
rootScope.on('history_append', (e) => {
@ -388,17 +379,6 @@ export class AppImManager { @@ -388,17 +379,6 @@ export class AppImManager {
this.renderMessage(appMessagesManager.getMessage(renderMaxID), true, false, this.bubbles[maxID], false);
});
rootScope.on('peer_pinned_message', (e) => {
const peerID = e.detail;
if(peerID == this.peerID) {
const promise: Promise<any> = this.setPeerPromise || this.messagesQueuePromise || Promise.resolve();
promise.then(() => {
this.setPinnedMessage();
});
}
});
rootScope.on('messages_downloaded', (e) => {
const mids: number[] = e.detail;
@ -464,9 +444,13 @@ export class AppImManager { @@ -464,9 +444,13 @@ export class AppImManager {
cancelEvent(e);
const mid = +pinned.dataset.mid;
const message = appMessagesManager.getMessage(mid);
if(pinned.classList.contains('pinned-message')) {
this.pinnedMessage.followPinnedMessage(mid);
} else {
const message = appMessagesManager.getMessage(mid);
this.setPeer(message.peerID, mid);
this.setPeer(message.peerID, mid);
}
} else {
appSidebarRight.toggleSidebar(true);
}
@ -688,7 +672,7 @@ export class AppImManager { @@ -688,7 +672,7 @@ export class AppImManager {
this.mutePeer(this.peerID);
});
this.btnJoin.addEventListener(CLICK_EVENT_NAME, (e) => {
attachClickEvent(this.btnJoin, (e) => {
cancelEvent(e);
this.btnJoin.setAttribute('disabled', 'true');
@ -906,20 +890,6 @@ export class AppImManager { @@ -906,20 +890,6 @@ export class AppImManager {
});
};
public setPinnedMessage() {
/////this.log('setting pinned message', message);
//return;
const message = appMessagesManager.getPinnedMessage(this.peerID);
if(message && !message.deleted) {
this.pinnedMessageContainer.fill('Pinned Message', message.message, message);
this.pinnedMessageContainer.toggle(false);
this.pinnedMsgID = message.mid;
} else if(this.pinnedMsgID) {
this.pinnedMsgID = 0;
this.pinnedMessageContainer.toggle(true);
}
}
public updateStatus() {
if(!this.myID) return Promise.resolve();
@ -973,39 +943,34 @@ export class AppImManager { @@ -973,39 +943,34 @@ export class AppImManager {
}
}
public onScroll() {
if(this.onScrollRAF) window.cancelAnimationFrame(this.onScrollRAF);
public onScroll = () => {
// * В таком случае, кнопка не будет моргать если чат в самом низу, и правильно отработает случай написания нового сообщения и проскролла вниз
if(this.scrollable.scrollLocked && this.scrolledDown) return;
this.onScrollRAF = window.requestAnimationFrame(() => {
//lottieLoader.checkAnimations(false, 'chat');
if(!isTouchSupported) {
if(this.isScrollingTimeout) {
clearTimeout(this.isScrollingTimeout);
} else if(!this.chatInner.classList.contains('is-scrolling')) {
this.chatInner.classList.add('is-scrolling');
}
this.isScrollingTimeout = window.setTimeout(() => {
this.chatInner.classList.remove('is-scrolling');
this.isScrollingTimeout = 0;
}, 1350);
if(!isTouchSupported) {
if(this.isScrollingTimeout) {
clearTimeout(this.isScrollingTimeout);
} else if(!this.chatInner.classList.contains('is-scrolling')) {
this.chatInner.classList.add('is-scrolling');
}
if(this.scrollable.isScrolledDown) {
this.bubblesContainer.classList.add('scrolled-down');
this.scrolledDown = true;
} else if(this.bubblesContainer.classList.contains('scrolled-down')) {
this.bubblesContainer.classList.remove('scrolled-down');
this.scrolledDown = false;
}
this.isScrollingTimeout = window.setTimeout(() => {
this.chatInner.classList.remove('is-scrolling');
this.isScrollingTimeout = 0;
}, 1350);
}
this.onScrollRAF = 0;
});
}
if(this.scrollable.isScrolledDown) {
this.bubblesContainer.classList.add('scrolled-down');
this.scrolledDown = true;
} else if(this.bubblesContainer.classList.contains('scrolled-down')) {
this.bubblesContainer.classList.remove('scrolled-down');
this.scrolledDown = false;
}
this.pinnedMessage.setCorrectIndex(this.scrollable.lastScrollDirection);
};
public setScroll() {
this.scrollable = new Scrollable(this.bubblesContainer/* .firstElementChild */ as HTMLElement, 'IM', 300);
@ -1024,11 +989,11 @@ export class AppImManager { @@ -1024,11 +989,11 @@ export class AppImManager {
this.bubblesContainer/* .firstElementChild */.append(this.goDownBtn);
this.scrollable.onAdditionalScroll = this.onScroll;
this.scrollable.onScrolledTop = () => this.loadMoreHistory(true);
this.scrollable.onScrolledBottom = () => this.loadMoreHistory(false);
//this.scrollable.attachSentinels(undefined, 300);
this.scroll.addEventListener('scroll', this.onScroll.bind(this));
this.bubblesContainer.classList.add('scrolled-down');
if(isTouchSupported) {
@ -1465,7 +1430,7 @@ export class AppImManager { @@ -1465,7 +1430,7 @@ export class AppImManager {
this.menuButtons.mute.style.display = this.myID == this.peerID ? 'none' : '';
this.setPinnedMessage();
this.pinnedMessage.setPinnedMessage();
window.requestAnimationFrame(() => {
/* noTransition.forEach(el => {

153
src/lib/appManagers/appMessagesManager.ts

@ -72,7 +72,8 @@ type MyInputMessagesFilter = 'inputMessagesFilterEmpty' @@ -72,7 +72,8 @@ type MyInputMessagesFilter = 'inputMessagesFilterEmpty'
| 'inputMessagesFilterMusic'
| 'inputMessagesFilterUrl'
| 'inputMessagesFilterMyMentions'
| 'inputMessagesFilterChatPhotos';
| 'inputMessagesFilterChatPhotos'
| 'inputMessagesFilterPinned';
export class AppMessagesManager {
public messagesStorage: {[mid: string]: any} = {};
@ -81,7 +82,10 @@ export class AppMessagesManager { @@ -81,7 +82,10 @@ export class AppMessagesManager {
public historiesStorage: {
[peerID: string]: HistoryStorage
} = {};
public pinnedMessages: {[peerID: string]: number} = {};
// * mids - descend sorted
public pinnedMessagesStorage: {[peerID: string]: Partial<{promise: Promise<number[]>, mids: number[]}>} = {};
public pendingByRandomID: {[randomID: string]: [number, number]} = {};
public pendingByMessageID: any = {};
public pendingAfterMsgs: any = {};
@ -1797,11 +1801,11 @@ export class AppMessagesManager { @@ -1797,11 +1801,11 @@ export class AppMessagesManager {
});
}
public savePinnedMessage(peerID: number, mid: number) {
/* public savePinnedMessage(peerID: number, mid: number) {
if(!mid) {
delete this.pinnedMessages[peerID];
delete this.pinnedMessagesStorage[peerID];
} else {
this.pinnedMessages[peerID] = mid;
this.pinnedMessagesStorage[peerID] = mid;
if(!this.messagesStorage.hasOwnProperty(mid)) {
this.wrapSingleMessage(mid).then(() => {
@ -1813,18 +1817,38 @@ export class AppMessagesManager { @@ -1813,18 +1817,38 @@ export class AppMessagesManager {
}
rootScope.broadcast('peer_pinned_message', peerID);
} */
public getPinnedMessagesStorage(peerID: number) {
return this.pinnedMessagesStorage[peerID] ?? (this.pinnedMessagesStorage[peerID] = {});
}
public getPinnedMessage(peerID: number) {
return this.getMessage(this.pinnedMessages[peerID] || 0);
public getPinnedMessages(peerID: number) {
const storage = this.getPinnedMessagesStorage(peerID);
if(storage.mids) {
return Promise.resolve(storage.mids);
} else if(storage.promise) {
return storage.promise;
}
return storage.promise = new Promise<number[]>((resolve, reject) => {
this.getSearch(peerID, '', {_: 'inputMessagesFilterPinned'}, 0, 50).then(result => {
resolve(storage.mids = result.history);
}, reject);
}).finally(() => {
storage.promise = null;
});
}
public updatePinnedMessage(peerID: number, msgID: number) {
public updatePinnedMessage(peerID: number, mid: number, unpin?: true, silent?: true, oneSide?: true) {
apiManager.invokeApi('messages.updatePinnedMessage', {
peer: appPeersManager.getInputPeerByID(peerID),
id: msgID
unpin,
silent,
pm_oneside: oneSide,
id: mid
}).then(updates => {
/////this.log('pinned updates:', updates);
this.log('pinned updates:', updates);
apiUpdatesManager.processUpdateMessage(updates);
});
}
@ -2667,7 +2691,8 @@ export class AppMessagesManager { @@ -2667,7 +2691,8 @@ export class AppMessagesManager {
//this.log(dT(), 'search', useSearchCache, sameSearchCache, this.lastSearchResults, maxID);
if(peerID && !maxID && !query) {
var historyStorage = this.historiesStorage[peerID];
const historyStorage = this.historiesStorage[peerID];
let filtering = true;
if(historyStorage !== undefined && historyStorage.history.length) {
var neededContents: {
@ -2729,49 +2754,53 @@ export class AppMessagesManager { @@ -2729,49 +2754,53 @@ export class AppMessagesManager {
break; */
default:
return Promise.resolve({
filtering = false;
break;
/* return Promise.resolve({
count: 0,
next_rate: 0,
history: [] as number[]
});
}); */
}
for(let i = 0, length = historyStorage.history.length; i < length; i++) {
const message = this.messagesStorage[historyStorage.history[i]];
if(filtering) {
for(let i = 0, length = historyStorage.history.length; i < length; i++) {
const message = this.messagesStorage[historyStorage.history[i]];
//|| (neededContents['mentioned'] && message.totalEntities.find((e: any) => e._ == 'messageEntityMention'));
//|| (neededContents['mentioned'] && message.totalEntities.find((e: any) => e._ == 'messageEntityMention'));
let found = false;
if(message.media && neededContents[message.media._] && !message.fwd_from) {
if(message.media._ == 'messageMediaDocument') {
if((neededDocTypes.length && !neededDocTypes.includes(message.media.document.type))
|| excludeDocTypes.includes(message.media.document.type)) {
continue;
let found = false;
if(message.media && neededContents[message.media._] && !message.fwd_from) {
if(message.media._ == 'messageMediaDocument') {
if((neededDocTypes.length && !neededDocTypes.includes(message.media.document.type))
|| excludeDocTypes.includes(message.media.document.type)) {
continue;
}
}
}
found = true;
} else if(neededContents['url'] && message.message) {
const goodEntities = ['messageEntityTextUrl', 'messageEntityUrl'];
if((message.totalEntities as MessageEntity[]).find(e => goodEntities.includes(e._)) || RichTextProcessor.matchUrl(message.message)) {
found = true;
} else if(neededContents['url'] && message.message) {
const goodEntities = ['messageEntityTextUrl', 'messageEntityUrl'];
if((message.totalEntities as MessageEntity[]).find(e => goodEntities.includes(e._)) || RichTextProcessor.matchUrl(message.message)) {
found = true;
}
} else if(neededContents['avatar'] && message.action && ['messageActionChannelEditPhoto', 'messageActionChatEditPhoto'].includes(message.action._)) {
found = true;
}
} else if(neededContents['avatar'] && message.action && ['messageActionChannelEditPhoto', 'messageActionChatEditPhoto'].includes(message.action._)) {
found = true;
}
if(found) {
foundMsgs.push(message.mid);
if(foundMsgs.length >= limit) {
break;
if(found) {
foundMsgs.push(message.mid);
if(foundMsgs.length >= limit) {
break;
}
}
}
}
}
// this.log.warn(dT(), 'before append', foundMsgs)
if(foundMsgs.length < limit && this.lastSearchResults.length && sameSearchCache) {
var minID = foundMsgs.length ? foundMsgs[foundMsgs.length - 1] : false;
if(filtering && foundMsgs.length < limit && this.lastSearchResults.length && sameSearchCache) {
let minID = foundMsgs.length ? foundMsgs[foundMsgs.length - 1] : false;
for(let i = 0; i < this.lastSearchResults.length; i++) {
if(minID === false || this.lastSearchResults[i] < minID) {
foundMsgs.push(this.lastSearchResults[i]);
@ -3435,19 +3464,19 @@ export class AppMessagesManager { @@ -3435,19 +3464,19 @@ export class AppMessagesManager {
});
const groupID = (message as Message.message).grouped_id;
if(this.pinnedMessages[peerID]) {
/* if(this.pinnedMessagesStorage[peerID]) {
let pinnedMid: number;
if(groupID) {
const mids = this.getMidsByAlbum(groupID);
pinnedMid = mids.find(mid => this.pinnedMessages[peerID] == mid);
} else if(this.pinnedMessages[peerID] == mid) {
pinnedMid = mids.find(mid => this.pinnedMessagesStorage[peerID] == mid);
} else if(this.pinnedMessagesStorage[peerID] == mid) {
pinnedMid = mid;
}
if(pinnedMid) {
rootScope.broadcast('peer_pinned_message', peerID);
}
}
} */
if(isTopMessage || groupID) {
const updatedDialogs: {[peerID: number]: Dialog} = {};
@ -3627,9 +3656,9 @@ export class AppMessagesManager { @@ -3627,9 +3656,9 @@ export class AppMessagesManager {
}
}
if(this.pinnedMessages[peerID] == mid) {
/* if(this.pinnedMessagesStorage[peerID] == mid) {
this.savePinnedMessage(peerID, 0);
}
} */
const peerMessagesToHandle = this.newMessagesToHandle[peerID];
if(peerMessagesToHandle && peerMessagesToHandle.length) {
@ -3791,12 +3820,40 @@ export class AppMessagesManager { @@ -3791,12 +3820,40 @@ export class AppMessagesManager {
break;
}
// 'updateChannelPinnedMessage' will be handled by appProfileManager
case 'updateChatPinnedMessage':
case 'updateUserPinnedMessage': {
// hz nado li tut appMessagesIDsManager.getFullMessageID(update.max_id, channelID);
const peerID = appPeersManager.getPeerID(update);
this.savePinnedMessage(peerID, update.id);
case 'updatePinnedMessages':
case 'updatePinnedChannelMessages': {
const channelID = update._ == 'updatePinnedChannelMessages' ? update.channel_id : undefined;
const peerID = channelID ? -channelID : appPeersManager.getPeerID((update as Update.updatePinnedMessages).peer);
const storage = this.getPinnedMessagesStorage(peerID);
if(!storage.mids) {
break;
}
const messages = channelID ? update.messages.map(messageID => appMessagesIDsManager.getFullMessageID(messageID, channelID)) : update.messages;
const missingMessages = messages.filter(mid => !this.messagesStorage[mid]);
const getMissingPromise = missingMessages.length ? Promise.all(missingMessages.map(mid => this.wrapSingleMessage(mid))) : Promise.resolve();
getMissingPromise.finally(() => {
const werePinned = update.pFlags?.pinned;
if(werePinned) {
for(const mid of messages) {
storage.mids.push(mid);
const message = this.messagesStorage[mid];
message.pFlags.pinned = true;
}
storage.mids.sort((a, b) => b - a);
} else {
for(const mid of messages) {
storage.mids.findAndSplice(_mid => _mid == mid);
const message = this.messagesStorage[mid];
delete message.pFlags.pinned;
}
}
rootScope.broadcast('peer_pinned_messages', peerID);
});
break;
}

2
src/lib/mtproto/schema.ts

File diff suppressed because one or more lines are too long

2
src/lib/rootScope.ts

@ -10,7 +10,7 @@ type BroadcastEvents = { @@ -10,7 +10,7 @@ type BroadcastEvents = {
'user_update': number,
'user_auth': UserAuth,
'peer_changed': number,
'peer_pinned_message': number,
'peer_pinned_messages': number,
'filter_delete': MyDialogFilter,
'filter_update': MyDialogFilter,

2
src/scripts/in/schema.json

File diff suppressed because one or more lines are too long

2
src/scripts/out/schema.json

File diff suppressed because one or more lines are too long

263
src/scss/partials/_chat.scss

@ -686,269 +686,6 @@ $chat-helper-size: 39px; @@ -686,269 +686,6 @@ $chat-helper-size: 39px;
}
}
}
.pinned-message, .reply {
cursor: pointer;
display: flex;
flex-direction: row;
align-items: center;
overflow: hidden;
box-sizing: border-box;
margin-right: 1rem;
max-height: 35px;
position: relative;
user-select: none;
/* padding: .25rem; */
&.is-media {
.pinned-message-content, .reply-content {
padding-left: 40px;
}
.emoji:first-child {
margin-right: .25rem;
}
}
&-border {
height: 2rem;
border-radius: 1px;
min-width: 2px;
background: $color-blue;
}
&-content {
margin-left: 8px;
flex-grow: 1;
flex-shrink: 1;
overflow: hidden;
pointer-events: none;
position: relative;
height: 2rem;
display: flex;
flex-direction: column;
justify-content: space-between;
}
&-title {
color: $color-blue;
}
&-title, &-subtitle {
font-size: 14px;
//line-height: 18px;
//line-height: 1;
//line-height: 15px;
line-height: 16px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
// @include respond-to(handhelds) {
// line-height: 13px;
// }
}
&-subtitle {
color: #111;
}
&-media {
height: 2rem;
width: 2rem;
border-radius: .5rem;
overflow: hidden;
position: absolute;
left: 0;
top: 0;
background-repeat: no-repeat;
background-size: cover;
background-position: center center;
/* > img, > video {
} */
// sticker
/* > img {
object-fit: contain !important;
max-width: 100%;
max-height: 100%;
} */
> img {
object-fit: cover;
width: 100%;
height: 100%;
}
}
i {
font-style: normal;
//color: $color-blue;
color: #707579;
/* &.document-title {
color: #707579;
} */
}
img.emoji {
height: 1rem;
width: 1rem;
vertical-align: top;
}
/* span.emoji {
font-size: 1rem;
vertical-align: unset;
} */
}
.reply {
html.no-touch &:hover {
background-color: var(--color-gray-hover);
}
}
.pinned-container {
flex: 0 0 auto;
overflow: visible;
&.is-floating {
position: absolute !important;
top: 100%;
width: 100% !important;
background: #fff !important;
left: 0;
max-height: 100% !important;
height: 52px;
.pinned-container-close {
position: absolute;
font-size: 1.4rem;
right: 9px;
display: flex;
}
.pinned-container-wrapper {
padding: 0 1rem;
height: 100%;
}
}
@include respond-to(handhelds) {
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, .15);
&:before {
width: 100%;
content: " ";
height: 52px;
left: 0;
top: 0;
position: absolute;
/* box-shadow: inset 0px 2px 3px 0px rgba(0, 0, 0, .15); */
box-shadow: inset 0px 1px 2px 0px rgba(0, 0, 0, .15);
}
}
&-content {
width: 100%;
overflow: hidden;
}
&-close, .pinned-audio-ico {
font-size: 1.5rem;
display: flex;
justify-content: center;
z-index: 1;
flex: 0 0 auto;
}
&-close {
display: none;
}
&-wrapper {
display: flex;
flex: 1 1 auto;
overflow: hidden;
align-items: center;
padding: .25rem;
border-radius: 4px;
html.no-touch &:hover {
background-color: var(--color-gray-hover);
}
}
}
.pinned-message {
display: none;
width: auto;
&:not(.is-floating) {
max-width: 239px;
.pinned-message-close {
display: flex;
margin-right: .75rem;
}
}
&.is-floating .pinned-message-subtitle {
max-width: 280px;
}
}
.pinned-audio {
display: flex;
flex-direction: column;
justify-content: center;
cursor: pointer;
//width: 210px;
&:not(.is-floating) {
padding-right: 1.75rem;
max-width: 210px;
position: relative;
}
&.is-floating .pinned-audio-ico {
margin-left: -.25rem;
}
&-ico {
color: #50a2e9;
margin-right: .375rem;
&:before {
content: $tgico-largeplay;
}
&.flip-icon:before {
content: $tgico-largepause;
}
}
&-title {
font-weight: 500;
width: 100%;
max-width: 100%;
}
&-subtitle {
color: #707579;
}
&-title, &-subtitle {
white-space: nowrap;
text-overflow: ellipsis;
font-size: 14px;
line-height: 1.4;
overflow: hidden;
max-width: 240px;
}
}
}
.input-message {

383
src/scss/partials/_chatPinned.scss

@ -0,0 +1,383 @@ @@ -0,0 +1,383 @@
.pinned-message {
&-border {
position: relative;
height: 35px;
width: 2px;
padding: 1.5px 0;
//padding: 0 0 6px 6px;
//overflow: hidden;
&-wrapper-1 {
height: 32px;
width: 2px;
border-radius: 1px;
background: $color-blue;
}
&-mask {
mask-image: linear-gradient(to bottom, transparent 0, black 6px, black 38px, transparent 44px);
}
&-wrapper {
color: $color-blue;
background: #50a2e988;
position: relative;
will-change: transform;
transition: transform 0.25s ease-in-out;
}
&-bars {
stroke: currentColor;
stroke-width: 2px;
stroke-linecap: round;
stroke-linejoin: round;
}
&-mark {
position: absolute;
left: 0;
top: 0;
width: 2px;
background: currentColor;
border-radius: 1px;
will-change: transform;
transition: transform 0.25s ease-in-out;
}
}
}
.pinned-message, .reply {
cursor: pointer;
display: flex;
flex-direction: row;
align-items: center;
overflow: hidden;
box-sizing: border-box;
margin-right: 1rem;
max-height: 35px;
position: relative;
user-select: none;
/* padding: .25rem; */
&.is-media {
.emoji:first-child {
margin-right: .25rem;
}
}
&-content {
margin-left: 8px;
flex-grow: 1;
flex-shrink: 1;
overflow: hidden;
pointer-events: none;
position: relative;
height: 2rem;
display: flex;
flex-direction: column;
justify-content: space-between;
}
&-title {
color: $color-blue;
}
&-title, &-subtitle {
font-size: 14px;
//line-height: 18px;
//line-height: 1;
//line-height: 15px;
line-height: 16px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
// @include respond-to(handhelds) {
// line-height: 13px;
// }
}
&-subtitle {
color: #111;
}
&-media {
height: 2rem;
width: 2rem;
border-radius: .5rem;
overflow: hidden;
position: absolute;
left: 0;
top: 0;
background-repeat: no-repeat;
background-size: cover;
background-position: center center;
/* > img, > video {
} */
// sticker
/* > img {
object-fit: contain !important;
max-width: 100%;
max-height: 100%;
} */
> img {
object-fit: cover;
width: 100%;
height: 100%;
}
}
i {
font-style: normal;
//color: $color-blue;
color: #707579;
/* &.document-title {
color: #707579;
} */
}
img.emoji {
height: 1rem;
width: 1rem;
vertical-align: top;
}
/* span.emoji {
font-size: 1rem;
vertical-align: unset;
} */
}
.reply {
&.is-media {
.reply-content {
padding-left: 2.5rem;
}
}
html.no-touch &:hover {
background-color: var(--color-gray-hover);
}
&-border {
height: 2rem;
border-radius: 1px;
min-width: 2px;
background: $color-blue;
}
}
.pinned-container {
flex: 0 0 auto;
overflow: visible;
&.is-floating {
position: absolute !important;
top: 100%;
width: 100% !important;
background: #fff !important;
left: 0;
max-height: 100% !important;
height: 52px;
.pinned-container-close {
position: absolute;
font-size: 1.4rem;
right: 9px;
display: flex;
}
.pinned-container-wrapper {
padding: 0 1rem;
height: 100%;
}
}
@include respond-to(handhelds) {
box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, .15);
&:before {
width: 100%;
content: " ";
height: 52px;
left: 0;
top: 0;
position: absolute;
/* box-shadow: inset 0px 2px 3px 0px rgba(0, 0, 0, .15); */
box-shadow: inset 0px 1px 2px 0px rgba(0, 0, 0, .15);
}
}
&-content {
width: 100%;
overflow: hidden;
}
&-close, .pinned-audio-ico {
font-size: 1.5rem;
display: flex;
justify-content: center;
z-index: 1;
flex: 0 0 auto;
}
&-close {
display: none;
}
&-wrapper {
display: flex;
flex: 1 1 auto;
overflow: hidden;
align-items: center;
padding: .25rem;
border-radius: 4px;
html.no-touch &:hover {
background-color: var(--color-gray-hover);
}
}
}
.pinned-message {
display: none;
width: auto;
&-media-container {
width: 2rem;
height: 2rem;
position: absolute;
transition: transform var(--pm-transition)/* , opacity var(--pm-transition) */;
}
/* &-media {
transform: none !important;
&.is-hiding {
&.backwards {
}
}
} */
// * fix blink in safari, can't add translateX from nowhere...
&-title, &-subtitle {
transform: translateX(0);
}
&.is-media {
.pinned-message-title, .pinned-message-subtitle {
transform: translateX(2.5rem);
}
}
&:not(.is-media) &-media-container {
transform: scale(0);
//opacity: 0;
}
&:not(.is-floating) {
width: 15.5rem;
.pinned-message-close {
display: flex;
margin-right: .75rem;
}
}
&.is-floating .pinned-message-subtitle {
max-width: 280px;
}
&-content {
> .pinned-message-title, > .pinned-message-subtitle {
position: relative;
height: 50%;
overflow: visible;
transition: transform var(--pm-transition);
}
}
&-subtitle {
.animated-super-row {
font-size: 14px;
line-height: 16px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
.animated-counter {
transition: transform var(--pm-transition), opacity var(--pm-transition);
&:before {
content: "#";
}
&.is-last {
&:not(.backwards) {
transform: scale(0.68);
opacity: 0;
}
/* .animated-super-row {
transition: none !important;
} */
}
}
}
.pinned-audio {
display: flex;
flex-direction: column;
justify-content: center;
cursor: pointer;
//width: 210px;
&:not(.is-floating) {
padding-right: 1.75rem;
max-width: 210px;
position: relative;
}
&.is-floating .pinned-audio-ico {
margin-left: -.25rem;
}
&-ico {
color: #50a2e9;
margin-right: .375rem;
&:before {
content: $tgico-largeplay;
}
&.flip-icon:before {
content: $tgico-largepause;
}
}
&-title {
font-weight: 500;
width: 100%;
max-width: 100%;
}
&-subtitle {
color: #707579;
}
&-title, &-subtitle {
white-space: nowrap;
text-overflow: ellipsis;
font-size: 14px;
line-height: 1.4;
overflow: hidden;
max-width: 240px;
}
}

2
src/scss/partials/_leftSidebar.scss

@ -74,7 +74,7 @@ @@ -74,7 +74,7 @@
overflow: visible;
i {
bottom: calc(-.625rem - -2px);
bottom: calc(-.625rem - -1.75px); // * 1.75px will fix for high DPR
padding-right: 1rem !important;
margin-left: -.5rem !important;
}

74
src/scss/style.scss

@ -77,6 +77,7 @@ $messages-container-width: 728px; @@ -77,6 +77,7 @@ $messages-container-width: 728px;
--z-below: -1;
--color-gray: #c4c9cc;
--color-gray-hover: rgba(112, 117, 121, .08);
--pm-transition: .2s ease-in-out;
--layer-transition: .2s ease-in-out;
//--layer-transition: .3s cubic-bezier(.33, 1, .68, 1);
//--layer-transition: none;
@ -108,6 +109,7 @@ $messages-container-width: 728px; @@ -108,6 +109,7 @@ $messages-container-width: 728px;
@import "partials/chatlist";
@import "partials/chat";
@import "partials/chatBubble";
@import "partials/chatPinned";
@import "partials/sidebar";
@import "partials/leftSidebar";
@import "partials/rightSidebar";
@ -804,6 +806,78 @@ img.emoji { @@ -804,6 +806,78 @@ img.emoji {
}
}
.animated-super {
&-row {
position: absolute;
left: 0;
top: 0;
right: 0;
bottom: 0;
transition: transform var(--pm-transition), opacity var(--pm-transition);
&.is-hiding {
opacity: 0;
&.from-top {
transform: translateY(-100%);
}
&.from-bottom {
transform: translateY(100%);
}
/* &.backwards {
opacity: 1;
transform: translateY(0) !important;
} */
}
}
}
.animated-counter {
display: inline-flex;
&-decimal {
position: relative;
&-placeholder {
color: transparent;
}
&-wrapper {
position: absolute;
left: 0;
top: 0;
bottom: 0;
right: 0;
}
/* &:not(:first-child) {
.animated-super {
&-row {
&.is-hiding {
&.from-top {
transform: translateY(100%);
}
&.from-bottom {
transform: translateY(-100%);
}
}
}
}
} */
}
/* &.from-top {
.animated-super-row.is-hiding {
&.from-top {
transform: translateY(100%) !important;
}
}
} */
}
.transition {
.transition-item {
position: absolute;

Loading…
Cancel
Save