diff --git a/src/components/chat/audio.ts b/src/components/chat/audio.ts index a85cdfb0..074a1ebe 100644 --- a/src/components/chat/audio.ts +++ b/src/components/chat/audio.ts @@ -1,65 +1,43 @@ -import appImManager from "../../lib/appManagers/appImManager"; import appMessagesManager from "../../lib/appManagers/appMessagesManager"; import appPeersManager from "../../lib/appManagers/appPeersManager"; import { RichTextProcessor } from "../../lib/richtextprocessor"; import $rootScope from "../../lib/rootScope"; import { cancelEvent } from "../../lib/utils"; import appMediaPlaybackController from "../appMediaPlaybackController"; +import DivAndCaption from "../divAndCaption"; import { formatDate } from "../wrappers"; +import PinnedContainer from "./pinnedContainer"; -export class ChatAudio { - public container: HTMLElement; - private toggle: HTMLElement; - private title: HTMLElement; - private subtitle: HTMLElement; - private close: HTMLElement; +export class ChatAudio extends PinnedContainer { + private toggleEl: HTMLElement; constructor() { - this.container = document.createElement('div'); - this.container.classList.add('pinned-audio', 'pinned-container'); - this.container.style.display = 'none'; - - this.toggle = document.createElement('div'); - this.toggle.classList.add('pinned-audio-ico', 'tgico'); - - this.title = document.createElement('div'); - this.title.classList.add('pinned-audio-title'); - - this.subtitle = document.createElement('div'); - this.subtitle.classList.add('pinned-audio-subtitle'); - - this.close = document.createElement('button'); - this.close.classList.add('pinned-audio-close', 'btn-icon', 'tgico-close'); - - this.container.append(this.toggle, this.title, this.subtitle, this.close); - - this.close.addEventListener('click', (e) => { - cancelEvent(e); - const scrollTop = appImManager.scrollable.scrollTop; - this.container.style.display = 'none'; - appImManager.topbar.classList.remove('is-audio-shown'); - if(this.toggle.classList.contains('flip-icon')) { + super('audio', new DivAndCaption('pinned-audio', (title: string, subtitle: string) => { + this.divAndCaption.title.innerHTML = title; + this.divAndCaption.subtitle.innerHTML = subtitle; + }), () => { + if(this.toggleEl.classList.contains('flip-icon')) { appMediaPlaybackController.toggle(); } - - if(!appImManager.topbar.classList.contains('is-pinned-shown')) { - appImManager.scrollable.scrollTop = scrollTop - height; - } }); - this.toggle.addEventListener('click', (e) => { + this.divAndCaption.border.remove(); + + this.toggleEl = document.createElement('div'); + this.toggleEl.classList.add('pinned-audio-ico', 'tgico'); + this.toggleEl.addEventListener('click', (e) => { cancelEvent(e); appMediaPlaybackController.toggle(); }); - const height = 52; + this.divAndCaption.container.prepend(this.toggleEl); $rootScope.$on('audio_play', (e) => { const {doc, mid} = e.detail; let title: string, subtitle: string; + const message = appMessagesManager.getMessage(mid); if(doc.type == 'voice' || doc.type == 'round') { - const message = appMessagesManager.getMessage(mid); title = appPeersManager.getPeerTitle(message.fromID, false, true); //subtitle = 'Voice message'; subtitle = formatDate(message.date, false, false); @@ -68,24 +46,13 @@ export class ChatAudio { subtitle = doc.audioPerformer ? RichTextProcessor.wrapPlainText(doc.audioPerformer) : 'Unknown Artist'; } - this.title.innerHTML = title; - this.subtitle.innerHTML = subtitle; - this.toggle.classList.add('flip-icon'); - - this.container.dataset.mid = '' + mid; - if(this.container.style.display) { - const scrollTop = appImManager.scrollable.scrollTop; - this.container.style.display = ''; - appImManager.topbar.classList.add('is-audio-shown'); - - if(!appImManager.topbar.classList.contains('is-pinned-shown')) { - appImManager.scrollable.scrollTop = scrollTop + height; - } - } + this.fill(title, subtitle, message); + this.toggleEl.classList.add('flip-icon'); + this.toggle(false); }); $rootScope.$on('audio_pause', () => { - this.toggle.classList.remove('flip-icon'); + this.toggleEl.classList.remove('flip-icon'); }); } } \ No newline at end of file diff --git a/src/components/chat/contextMenu.ts b/src/components/chat/contextMenu.ts index c4f65b27..9aa15ec0 100644 --- a/src/components/chat/contextMenu.ts +++ b/src/components/chat/contextMenu.ts @@ -77,7 +77,15 @@ export class ChatContextMenu { icon: 'pin', text: 'Pin', onClick: this.onPinClick, - verify: (peerID: number) => peerID == $rootScope.myID || (peerID < 0 && appChatsManager.hasRights(-peerID, 'pin')) + verify: (peerID: number, msgID: number) => { + const message = appMessagesManager.getMessage(msgID); + return message._ != 'messageService' && appImManager.pinnedMsgID != msgID && (peerID == $rootScope.myID || (peerID < 0 && appChatsManager.hasRights(-peerID, 'pin'))); + } + }, { + icon: 'unpin', + text: 'Unpin', + onClick: this.onUnpinClick, + verify: (peerID: number, msgID: number) => appImManager.pinnedMsgID == msgID && (peerID == $rootScope.myID || (peerID < 0 && appChatsManager.hasRights(-peerID, 'pin'))) }, { icon: 'revote', text: 'Revote', @@ -154,6 +162,10 @@ export class ChatContextMenu { appMessagesManager.updatePinnedMessage($rootScope.selectedPeerID, this.msgID); }; + private onUnpinClick = () => { + appMessagesManager.updatePinnedMessage($rootScope.selectedPeerID, 0); + }; + private onRetractVote = () => { appPollsManager.sendVote(this.msgID, []); }; diff --git a/src/components/chat/pinnedContainer.ts b/src/components/chat/pinnedContainer.ts new file mode 100644 index 00000000..b585dd32 --- /dev/null +++ b/src/components/chat/pinnedContainer.ts @@ -0,0 +1,61 @@ +import mediaSizes from "../../helpers/mediaSizes"; +import appImManager from "../../lib/appManagers/appImManager"; +import { cancelEvent } from "../../lib/utils"; +import DivAndCaption from "../divAndCaption"; + +const CLASSNAME_SHOWN_MAIN = 'is-pinned-message-shown'; +const CLASSNAME_BASE = 'pinned-container'; +const HEIGHT = 52; + +export default class PinnedContainer { + private close: HTMLElement; + + constructor(protected className: string, public divAndCaption: DivAndCaption<(title: string, subtitle: string, message?: any) => void>, onClose?: () => void | Promise) { + /* const prev = this.divAndCaption.fill; + this.divAndCaption.fill = (mid, title, subtitle) => { + this.divAndCaption.container.dataset.mid = '' + mid; + prev(mid, title, subtitle); + }; */ + + divAndCaption.container.classList.add(CLASSNAME_BASE, 'hide'); + divAndCaption.title.classList.add(CLASSNAME_BASE + '-title'); + divAndCaption.subtitle.classList.add(CLASSNAME_BASE + '-subtitle'); + + this.close = document.createElement('button'); + this.close.classList.add(CLASSNAME_BASE + '-close', `pinned-${className}-close`, 'btn-icon', 'tgico-close'); + + divAndCaption.container.append(this.close); + + this.close.addEventListener('click', (e) => { + cancelEvent(e); + + ((onClose ? onClose() : null) || Promise.resolve(true)).then(needClose => { + if(needClose) { + this.toggle(true); + } + }); + }); + } + + public toggle(hide?: boolean) { + const isHidden = this.divAndCaption.container.classList.contains('hide'); + if(hide === undefined) { + hide = !isHidden; + } else if(hide == isHidden) { + return; + } + + const scrollTop = mediaSizes.isMobile ? appImManager.scrollable.scrollTop : undefined; + this.divAndCaption.container.classList.toggle('hide', hide); + appImManager.topbar.classList.toggle(`is-pinned-${this.className}-shown`, !hide); + + if(scrollTop !== undefined && !appImManager.topbar.classList.contains(CLASSNAME_SHOWN_MAIN)) { + appImManager.scrollable.scrollTop = scrollTop + ((hide ? -1 : 1) * HEIGHT); + } + } + + public fill(title: string, subtitle: string, message: any) { + this.divAndCaption.container.dataset.mid = '' + message.mid; + this.divAndCaption.fill(title, subtitle, message); + } +} diff --git a/src/components/chat/replyContainer.ts b/src/components/chat/replyContainer.ts new file mode 100644 index 00000000..b3996cbe --- /dev/null +++ b/src/components/chat/replyContainer.ts @@ -0,0 +1,59 @@ +import appPhotosManager from "../../lib/appManagers/appPhotosManager"; +import { RichTextProcessor } from "../../lib/richtextprocessor"; +import DivAndCaption from "../divAndCaption"; +import { renderImageFromUrl } from "../misc"; + +export default class ReplyContainer extends DivAndCaption<(title: string, subtitle: string, message?: any) => void> { + private mediaEl: HTMLElement; + + constructor(protected className: string) { + super(className, (title: string, subtitle: string = '', message?: any) => { + if(title.length > 150) { + title = title.substr(0, 140) + '...'; + } + + if(subtitle.length > 150) { + subtitle = subtitle.substr(0, 140) + '...'; + } + + title = title ? RichTextProcessor.wrapEmojiText(title) : ''; + + if(this.mediaEl) { + this.mediaEl.remove(); + this.container.classList.remove('is-media'); + } + + const media = message && message.media; + if(media) { + subtitle = message.rReply; + + //console.log('wrap reply', media); + + if(media.photo || (media.document && ['video'].indexOf(media.document.type) !== -1)) { + let replyMedia = document.createElement('div'); + replyMedia.classList.add(this.className + '-media'); + + let photo = media.photo || media.document; + + let sizes = photo.sizes || photo.thumbs; + if(sizes && sizes[0].bytes) { + appPhotosManager.setAttachmentPreview(sizes[0].bytes, replyMedia, false, true); + } + + appPhotosManager.preloadPhoto(photo, appPhotosManager.choosePhotoSize(photo, 32, 32)) + .then(() => { + renderImageFromUrl(replyMedia, photo._ == 'photo' ? photo.url : appPhotosManager.getDocumentCachedThumb(photo.id).url); + }); + + this.content.prepend(this.mediaEl = replyMedia); + this.container.classList.add('is-media'); + } + } else { + subtitle = subtitle ? RichTextProcessor.wrapEmojiText(subtitle) : ''; + } + + this.title.innerHTML = title; + this.subtitle.innerHTML = subtitle; + }); + } +} \ No newline at end of file diff --git a/src/components/divAndCaption.ts b/src/components/divAndCaption.ts new file mode 100644 index 00000000..2428befc --- /dev/null +++ b/src/components/divAndCaption.ts @@ -0,0 +1,27 @@ +export default class DivAndCaption { + public container: HTMLElement; + public border: HTMLElement; + public content: HTMLElement; + public title: HTMLElement; + public subtitle: HTMLElement; + + constructor(protected className: string, public fill: T) { + this.container = document.createElement('div'); + this.container.className = className; + + this.border = document.createElement('div'); + this.border.classList.add(className + '-border'); + + this.content = document.createElement('div'); + this.content.classList.add(className + '-content'); + + this.title = document.createElement('div'); + this.title.classList.add(className + '-title'); + + this.subtitle = document.createElement('div'); + this.subtitle.classList.add(className + '-subtitle'); + + this.content.append(this.title, this.subtitle); + this.container.append(this.border, this.content); + } +} \ No newline at end of file diff --git a/src/components/scrollable.ts b/src/components/scrollable.ts index 3d80c6e1..47bfba73 100644 --- a/src/components/scrollable.ts +++ b/src/components/scrollable.ts @@ -104,17 +104,22 @@ export class ScrollableBase { return; } - if(this.scrollLocked) clearTimeout(this.scrollLocked); - else { - this.scrollLockedPromise = deferredPromise(); - } - - this.scrollLocked = window.setTimeout(() => { - this.scrollLocked = 0; + const wasLocked = !!this.scrollLocked; + if(wasLocked) clearTimeout(this.scrollLocked); + if(smooth) { + if(!wasLocked) { + this.scrollLockedPromise = deferredPromise(); + } + + this.scrollLocked = window.setTimeout(() => { + this.scrollLocked = 0; + this.scrollLockedPromise.resolve(); + //this.onScroll(); + this.container.dispatchEvent(new CustomEvent('scroll')); + }, scrollTime); + } else if(wasLocked) { this.scrollLockedPromise.resolve(); - //this.onScroll(); - this.container.dispatchEvent(new CustomEvent('scroll')); - }, scrollTime); + } const options: SmoothScrollToOptions = { behavior: smooth ? 'smooth' : 'auto', @@ -124,6 +129,10 @@ export class ScrollableBase { options[side] = value; this.container.scrollTo(options as any); + + if(!smooth) { + this.container.dispatchEvent(new CustomEvent('scroll')); + } } get length() { @@ -490,6 +499,10 @@ export default class Scrollable extends ScrollableBase { return this.scrollTop; }; + get isScrolledDown() { + return this.scrollHeight - Math.round(this.scrollTop + this.container.offsetHeight) <= 1; + } + set scrollTop(y: number) { this.container.scrollTop = y; } diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index dc1ce352..2d78984e 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -1,26 +1,26 @@ -import appPhotosManager, {MyPhoto} from '../lib/appManagers/appPhotosManager'; -import LottieLoader from '../lib/lottieLoader'; +import { readBlobAsText } from '../helpers/blob'; +import { deferredPromise } from '../helpers/cancellablePromise'; +import { months } from '../helpers/date'; +import mediaSizes from '../helpers/mediaSizes'; +import { isSafari } from '../helpers/userAgent'; +import { PhotoSize } from '../layer'; import appDocsManager, { MyDocument } from "../lib/appManagers/appDocsManager"; +import { DownloadBlob } from '../lib/appManagers/appDownloadManager'; +import appMessagesManager from '../lib/appManagers/appMessagesManager'; +import appPhotosManager, { MyPhoto } from '../lib/appManagers/appPhotosManager'; +import LottieLoader from '../lib/lottieLoader'; +import VideoPlayer from '../lib/mediaPlayer'; import { formatBytes, getEmojiToneIndex, isInDOM } from "../lib/utils"; -import ProgressivePreloader from './preloader'; +import webpWorkerController from '../lib/webp/webpWorkerController'; +import animationIntersector from './animationIntersector'; +import appMediaPlaybackController from './appMediaPlaybackController'; +import AudioElement from './audio'; +import ReplyContainer from './chat/replyContainer'; +import { Layouter, RectPart } from './groupedLayout'; import LazyLoadQueue from './lazyLoadQueue'; -import VideoPlayer from '../lib/mediaPlayer'; -import { RichTextProcessor } from '../lib/richtextprocessor'; import { renderImageFromUrl } from './misc'; -import appMessagesManager from '../lib/appManagers/appMessagesManager'; -import { Layouter, RectPart } from './groupedLayout'; import PollElement from './poll'; -import animationIntersector from './animationIntersector'; -import AudioElement from './audio'; -import { DownloadBlob } from '../lib/appManagers/appDownloadManager'; -import webpWorkerController from '../lib/webp/webpWorkerController'; -import { readBlobAsText } from '../helpers/blob'; -import appMediaPlaybackController from './appMediaPlaybackController'; -import { PhotoSize } from '../layer'; -import { deferredPromise } from '../helpers/cancellablePromise'; -import mediaSizes from '../helpers/mediaSizes'; -import { isSafari } from '../helpers/userAgent'; -import { months } from '../helpers/date'; +import ProgressivePreloader from './preloader'; export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue, noInfo, group}: { doc: MyDocument, @@ -697,68 +697,11 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o return lazyLoadQueue && (!doc.downloaded || stickerType == 2) ? (lazyLoadQueue.push({div, load, wasSeen: group == 'chat' && stickerType != 2}), Promise.resolve()) : load(); } -export function wrapReply(title: string, subtitle: string, message?: any, isPinned?: boolean) { - const prefix = isPinned ? 'pinned-message' : 'reply'; - const div = document.createElement('div'); - div.classList.add(prefix); - - const replyBorder = document.createElement('div'); - replyBorder.classList.add(prefix + '-border'); - - const replyContent = document.createElement('div'); - replyContent.classList.add(prefix + '-content'); - - const replyTitle = document.createElement('div'); - replyTitle.classList.add(prefix + '-title'); - - const replySubtitle = document.createElement('div'); - replySubtitle.classList.add(prefix + '-subtitle'); - - if(title.length > 150) { - title = title.substr(0, 140) + '...'; - } - - if(subtitle.length > 150) { - subtitle = subtitle.substr(0, 140) + '...'; - } - - replyTitle.innerHTML = title ? RichTextProcessor.wrapEmojiText(title) : ''; - - const media = message && message.media; - if(media) { - replySubtitle.innerHTML = message.rReply; - - //console.log('wrap reply', media); - - if(media.photo || (media.document && ['video'].indexOf(media.document.type) !== -1)) { - let replyMedia = document.createElement('div'); - replyMedia.classList.add(prefix + '-media'); - - let photo = media.photo || media.document; - - let sizes = photo.sizes || photo.thumbs; - if(sizes && sizes[0].bytes) { - appPhotosManager.setAttachmentPreview(sizes[0].bytes, replyMedia, false, true); - } - - appPhotosManager.preloadPhoto(photo, appPhotosManager.choosePhotoSize(photo, 32, 32)) - .then(() => { - renderImageFromUrl(replyMedia, photo._ == 'photo' ? photo.url : appPhotosManager.getDocumentCachedThumb(photo.id).url); - }); - - replyContent.append(replyMedia); - div.classList.add('is-media'); - } - } else { - replySubtitle.innerHTML = subtitle ? RichTextProcessor.wrapEmojiText(subtitle) : ''; - } - - replyContent.append(replyTitle, replySubtitle); - div.append(replyBorder, replyContent); - +export function wrapReply(title: string, subtitle: string, message?: any) { + const replyContainer = new ReplyContainer('reply'); + replyContainer.fill(title, subtitle, message); /////////console.log('wrapReply', title, subtitle, media); - - return div; + return replyContainer.container; } export function wrapAlbum({groupID, attachmentDiv, middleware, uploading, lazyLoadQueue, isOut}: { diff --git a/src/lib/appManagers/appChatsManager.ts b/src/lib/appManagers/appChatsManager.ts index 19aeae5b..81ce5f06 100644 --- a/src/lib/appManagers/appChatsManager.ts +++ b/src/lib/appManagers/appChatsManager.ts @@ -1,4 +1,4 @@ -import { ChatBannedRights, InputChannel, InputChatPhoto, InputPeer, Updates } from "../../layer"; +import { ChatAdminRights, ChatBannedRights, InputChannel, InputChatPhoto, InputPeer, Updates } from "../../layer"; import apiManager from '../mtproto/mtprotoworker'; import { RichTextProcessor } from "../richtextprocessor"; import $rootScope from "../rootScope"; @@ -166,17 +166,19 @@ export class AppChatsManager { return true; } - let myFlags = (chat.admin_rights || chat.banned_rights || chat.default_banned_rights)?.pFlags ?? {}; + const rights = chat.admin_rights || chat.banned_rights || chat.default_banned_rights; + let myFlags: {[flag in keyof ChatBannedRights['pFlags'] | keyof ChatAdminRights['pFlags']]: true}; + if(rights) myFlags = rights.pFlags; switch(action) { // good case 'send': { - if(flag && myFlags[flag]) { + if(flag && myFlags && myFlags[flag]) { return false; } if(chat._ == 'channel') { - if((!chat.pFlags.megagroup && !myFlags.post_messages)) { + if((!chat.pFlags.megagroup && !myFlags?.post_messages)) { return false; } } @@ -187,7 +189,7 @@ export class AppChatsManager { // good case 'deleteRevoke': { if(chat._ == 'channel') { - return !!myFlags.delete_messages; + return !!myFlags?.delete_messages; } else if(!chat.pFlags.admin) { return false; } @@ -198,9 +200,9 @@ export class AppChatsManager { // good case 'pin': { if(chat._ == 'channel') { - return chat.admin_rights ? !!myFlags.pin_messages || !!myFlags.post_messages : !!myFlags.pin_messages; + return chat.admin_rights ? !!myFlags.pin_messages || !!myFlags.post_messages : myFlags && !myFlags.pin_messages; } else { - if(myFlags.pin_messages && !chat.pFlags.admin) { + if(myFlags?.pin_messages && !chat.pFlags.admin) { return false; } } diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 2179380a..1d056967 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -6,6 +6,8 @@ import BubbleGroups from '../../components/bubbleGroups'; import { ChatAudio } from '../../components/chat/audio'; import { ChatContextMenu } from '../../components/chat/contextMenu'; import { ChatInput } from '../../components/chat/input'; +import PinnedContainer from '../../components/chat/pinnedContainer'; +import ReplyContainer from '../../components/chat/replyContainer'; import { ChatSearch } from '../../components/chat/search'; import { horizontalMenu } from '../../components/horizontalMenu'; import LazyLoadQueue from '../../components/lazyLoadQueue'; @@ -94,7 +96,7 @@ export class AppImManager { public updateStatusInterval = 0; public pinnedMsgID = 0; - private pinnedMessageContainer: HTMLDivElement = null; + private pinnedMessageContainer: PinnedContainer = null; public lazyLoadQueue = new LazyLoadQueue(); @@ -165,7 +167,10 @@ export class AppImManager { parseMenuButtonsTo(this.menuButtons, this.columnEl.querySelector('.chat-more-button').firstElementChild.children); this.chatAudio = new ChatAudio(); - this.chatInfo.nextElementSibling.prepend(this.chatAudio.container); + this.chatInfo.nextElementSibling.prepend(this.chatAudio.divAndCaption.container); + + this.pinnedMessageContainer = new PinnedContainer('message', new ReplyContainer('pinned-message')); + this.btnJoin.parentElement.insertBefore(this.pinnedMessageContainer.divAndCaption.container, this.btnJoin); // will call when message is sent (only 1) $rootScope.$on('history_append', (e) => { @@ -323,17 +328,20 @@ export class AppImManager { this.renderMessage(message, true, false, bubble, false); }); + $rootScope.$on('peer_pinned_message', (e) => { + const peerID = e.detail; + + if(peerID == this.peerID) { + (this.messagesQueuePromise || Promise.resolve()).then(() => { + this.setPinnedMessage(); + }); + } + }); + $rootScope.$on('messages_downloaded', (e) => { const mids: number[] = e.detail; - const pinnedMessage = appMessagesManager.getPinnedMessage(this.peerID); mids.forEach(mid => { - if(pinnedMessage.mid == mid) { - (this.messagesQueuePromise || Promise.resolve()).then(() => { - this.setPinnedMessage(pinnedMessage); - }); - } - /* const promise = (this.scrollable.scrollLocked && this.scrollable.scrollLockedPromise) || Promise.resolve(); promise.then(() => { @@ -747,38 +755,18 @@ export class AppImManager { }); }; - public setPinnedMessage(message: any) { + public setPinnedMessage() { /////this.log('setting pinned message', message); //return; - const height = 52; - const scrollTop = this.scrollable.container.scrollTop; - const newPinned = wrapReply('Pinned Message', message.message, message, true); - newPinned.dataset.mid = '' + message.mid; - newPinned.classList.add('pinned-container'); - - const close = document.createElement('button'); - close.classList.add('pinned-message-close', 'btn-icon', 'tgico-close'); - close.addEventListener('click', (e) => { - cancelEvent(e); - const scrollTop = this.scrollable.scrollTop; - newPinned.remove(); - this.topbar.classList.remove('is-pinned-shown'); - - this.pinnedMessageContainer = null; - this.scrollable.scrollTop = scrollTop - height; - }, {once: true}); - newPinned.append(close); - - this.btnJoin.parentElement.insertBefore(newPinned, this.btnJoin); - this.topbar.classList.add('is-pinned-shown'); - - if(this.pinnedMessageContainer) { - this.pinnedMessageContainer.remove(); + 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); } - - this.pinnedMessageContainer = newPinned; - //this.pinnedMessageContent.innerHTML = message.rReply; - this.scrollable.scrollTop = scrollTop + height; } public updateStatus() { @@ -851,7 +839,7 @@ export class AppImManager { }, 1350); } - if(this.scroll.scrollHeight - Math.round(this.scroll.scrollTop + this.scroll.offsetHeight) <= 1/* <= 5 */) { + if(this.scrollable.isScrolledDown) { this.scroll.parentElement.classList.add('scrolled-down'); this.scrolledDown = true; } else if(this.scroll.parentElement.classList.contains('scrolled-down')) { @@ -1289,13 +1277,7 @@ export class AppImManager { this.menuButtons.mute.style.display = this.myID == this.peerID ? 'none' : ''; - const pinned = appMessagesManager.getPinnedMessage(peerID); - if(pinned && !pinned.deleted) { - this.setPinnedMessage(pinned); - } else if(this.pinnedMessageContainer) { - this.pinnedMessageContainer.remove(); - this.pinnedMessageContainer = null; - } + this.setPinnedMessage(); window.requestAnimationFrame(() => { let title = ''; @@ -1370,9 +1352,13 @@ export class AppImManager { //if(scrolledDown) this.scrollable.scrollTop = this.scrollable.scrollHeight; if(this.messagesQueuePromise && scrolledDown) { - this.scrollable.scrollTo(this.scrollable.scrollHeight - 1, 'top', false, true); + if(this.scrollable.isScrolledDown && !this.scrollable.scrollLocked) { + //this.log('renderNewMessagesByIDs: messagesQueuePromise before will set prev max'); + this.scrollable.scrollTo(this.scrollable.scrollHeight - 1, 'top', false, true); + } + this.messagesQueuePromise.then(() => { - //this.log('messagesQueuePromise after:', this.chatInner.childElementCount, this.scrollable.scrollHeight); + //this.log('renderNewMessagesByIDs: messagesQueuePromise after', this.scrollable.isScrolledDown); this.scrollable.scrollTo(this.scrollable.scrollHeight, 'top', true, true); /* setTimeout(() => { diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index af9c77bb..0300e447 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -2162,11 +2162,19 @@ export class AppMessagesManager { public savePinnedMessage(peerID: number, mid: number) { if(!mid) { delete this.pinnedMessages[peerID]; - return; + } else { + this.pinnedMessages[peerID] = mid; + + if(!this.messagesStorage.hasOwnProperty(mid)) { + this.wrapSingleMessage(mid).then(() => { + $rootScope.$broadcast('peer_pinned_message', peerID); + }); + + return; + } } - this.pinnedMessages[peerID] = mid; - this.wrapSingleMessage(mid); + $rootScope.$broadcast('peer_pinned_message', peerID); } public getPinnedMessage(peerID: number) { diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index 60f58889..da8fb19c 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -17,13 +17,13 @@ // border-bottom: 1px solid #DADCE0; @include respond-to(handhelds) { - &.is-audio-shown, &.is-pinned-shown:not(.hide-pinned) { + &.is-pinned-audio-shown, &.is-pinned-message-shown:not(.hide-pinned) { & + #bubbles { margin-top: 52px; } } - &.is-pinned-shown:not(.hide-pinned):not(.is-audio-shown) { + &.is-pinned-message-shown:not(.hide-pinned):not(.is-pinned-audio-shown) { .pinned-message { display: flex; } @@ -38,7 +38,7 @@ display: none; } - &.is-pinned-shown:not(.hide-pinned) { + &.is-pinned-message-shown:not(.hide-pinned) { .pinned-message { display: flex; } @@ -102,18 +102,18 @@ } @include respond-to(medium-screens) { - &.is-pinned-shown { + &.is-pinned-message-shown { body.is-right-column-shown & { .chat-info { max-width: calc(100% - var(--right-column-width) * 1.75); } } - } - &.is-pinned-shown.is-audio-shown { - body.is-right-column-shown & { - .chat-info { - max-width: calc(100% - var(--right-column-width) * 2.25); + &.is-pinned-audio-shown { + body.is-right-column-shown & { + .chat-info { + max-width: calc(100% - var(--right-column-width) * 2.25); + } } } } @@ -792,6 +792,7 @@ .pinned-container { flex: 0 0 auto; + overflow: visible; @include respond-to(handhelds) { box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.15); @@ -808,21 +809,22 @@ } } - .pinned-message-close, .pinned-audio-close, .pinned-audio-ico { + &-close, .pinned-audio-ico { font-size: 1.5rem; position: absolute; display: flex; justify-content: center; right: 0; + } - &.tgico-close { - visibility: hidden; + &-close { + visibility: hidden; + //left: -3rem; - @include respond-to(handhelds) { - font-size: 1.4rem; - right: 9px; - visibility: visible; - } + @include respond-to(handhelds) { + font-size: 1.4rem; + right: 9px; + visibility: visible; } } }