diff --git a/src/components/appSearch.ts b/src/components/appSearch.ts
index e050f821..5cbba219 100644
--- a/src/components/appSearch.ts
+++ b/src/components/appSearch.ts
@@ -7,7 +7,6 @@ import appMessagesManager from "../lib/appManagers/appMessagesManager";
import { formatPhoneNumber } from "./misc";
import appChatsManager from "../lib/appManagers/appChatsManager";
import SearchInput from "./searchInput";
-import { Peer } from "../layer";
import rootScope from "../lib/rootScope";
import { escapeRegExp } from "../helpers/string";
import searchIndexManager from "../lib/searchIndexManager";
@@ -237,7 +236,7 @@ export default class AppSearch {
});
}
- return this.searchPromise = appMessagesManager.getSearch(this.peerID, query, null, maxID, 20, this.offsetRate).then(res => {
+ return this.searchPromise = appMessagesManager.getSearch(this.peerID, query, {_: 'inputMessagesFilterEmpty'}, maxID, 20, this.offsetRate).then(res => {
this.searchPromise = null;
if(this.searchInput.value != query) {
diff --git a/src/components/appSelectPeers.ts b/src/components/appSelectPeers.ts
index 3d672994..c8e38d4d 100644
--- a/src/components/appSelectPeers.ts
+++ b/src/components/appSelectPeers.ts
@@ -320,7 +320,7 @@ export default class AppSelectPeers {
if(this.multiSelect) {
const selected = this.selected.has(peerID);
- dom.containerEl.insertAdjacentHTML('afterbegin', `
`);
+ dom.containerEl.insertAdjacentHTML('afterbegin', ``);
if(selected) dom.listEl.classList.add('active');
}
diff --git a/src/components/avatar.ts b/src/components/avatar.ts
index c57d2326..5f19d73e 100644
--- a/src/components/avatar.ts
+++ b/src/components/avatar.ts
@@ -3,6 +3,7 @@ import appProfileManager from "../lib/appManagers/appProfileManager";
import rootScope from "../lib/rootScope";
import { cancelEvent } from "../helpers/dom";
import AppMediaViewer, { AppMediaViewerAvatar } from "./appMediaViewer";
+import { Photo } from "../layer";
rootScope.on('avatar_update', (e) => {
let peerID = e.detail;
@@ -66,6 +67,8 @@ export default class AvatarElement extends HTMLElement {
_: 'messageMediaPhoto',
photo: photo
},
+ peerID,
+ date: (photo as Photo.photo).date,
fromID: peerID
};
diff --git a/src/components/bubbleGroups.ts b/src/components/bubbleGroups.ts
index cd57409b..1d291243 100644
--- a/src/components/bubbleGroups.ts
+++ b/src/components/bubbleGroups.ts
@@ -1,8 +1,9 @@
import rootScope from "../lib/rootScope";
import { generatePathData } from "../helpers/dom";
+type BubbleGroup = {timestamp: number, fromID: number, mid: number, group: HTMLDivElement[]};
export default class BubbleGroups {
- bubblesByGroups: Array<{timestamp: number, fromID: number, mid: number, group: HTMLDivElement[]}> = []; // map to group
+ bubblesByGroups: Array = []; // map to group
groups: Array = [];
//updateRAFs: Map = new Map();
newGroupDiff = 120;
@@ -62,8 +63,9 @@ export default class BubbleGroups {
setClipIfNeeded(bubble: HTMLDivElement, remove = false) {
//console.log('setClipIfNeeded', bubble, remove);
- if(bubble.classList.contains('is-message-empty')/* && !bubble.classList.contains('is-reply') */
- && (bubble.classList.contains('photo') || bubble.classList.contains('video'))) {
+ const className = bubble.className;
+ if(className.includes('is-message-empty')/* && !className.includes('is-reply') */
+ && (className.includes('photo') || className.includes('video'))) {
let container = bubble.querySelector('.bubble__media-container') as SVGSVGElement;
//console.log('setClipIfNeeded', bubble, remove, container);
if(!container) return;
@@ -78,21 +80,21 @@ export default class BubbleGroups {
let path = container.firstElementChild.firstElementChild.lastElementChild as SVGPathElement;
let width = +object.getAttributeNS(null, 'width');
let height = +object.getAttributeNS(null, 'height');
- let isOut = bubble.classList.contains('is-out');
- let isReply = bubble.classList.contains('is-reply');
+ let isOut = className.includes('is-out');
+ let isReply = className.includes('is-reply');
let d = '';
//console.log('setClipIfNeeded', object, width, height, isOut);
let tr: number, tl: number;
- if(bubble.classList.contains('forwarded') || isReply) {
+ if(className.includes('forwarded') || isReply) {
tr = tl = 0;
} else if(isOut) {
- tr = bubble.classList.contains('is-group-first') ? 12 : 6;
+ tr = className.includes('is-group-first') ? 12 : 6;
tl = 12;
} else {
tr = 12;
- tl = bubble.classList.contains('is-group-first') ? 12 : 6;
+ tl = className.includes('is-group-first') ? 12 : 6;
}
if(isOut) {
diff --git a/src/components/chat/audio.ts b/src/components/chat/audio.ts
index 0ca025c5..d0058521 100644
--- a/src/components/chat/audio.ts
+++ b/src/components/chat/audio.ts
@@ -14,7 +14,7 @@ export default class ChatAudio extends PinnedContainer {
private toggleEl: HTMLElement;
constructor(protected topbar: ChatTopbar, protected chat: Chat, protected appMessagesManager: AppMessagesManager, protected appPeersManager: AppPeersManager) {
- super(topbar, chat, 'audio', new DivAndCaption('pinned-audio', (title: string, subtitle: string) => {
+ super(topbar, chat, topbar.listenerSetter, 'audio', new DivAndCaption('pinned-audio', (title: string, subtitle: string) => {
this.divAndCaption.title.innerHTML = title;
this.divAndCaption.subtitle.innerHTML = subtitle;
}), () => {
diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts
index 37272cf7..0450a12d 100644
--- a/src/components/chat/bubbles.ts
+++ b/src/components/chat/bubbles.ts
@@ -1,4 +1,4 @@
-import { AppImManager, CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
+import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
import type { AppMessagesManager, Dialog, HistoryResult } from "../../lib/appManagers/appMessagesManager";
import type { AppSidebarRight } from "../sidebarRight";
import type { AppStickersManager } from "../../lib/appManagers/appStickersManager";
@@ -7,22 +7,19 @@ import type { AppInlineBotsManager } from "../../lib/appManagers/AppInlineBotsMa
import type { AppPhotosManager } from "../../lib/appManagers/appPhotosManager";
import type { AppDocsManager } from "../../lib/appManagers/appDocsManager";
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
-import { findUpClassName, cancelEvent, findUpTag, CLICK_EVENT_NAME, whichChild } from "../../helpers/dom";
+import { findUpClassName, cancelEvent, findUpTag, CLICK_EVENT_NAME, whichChild, getElementByPoint } from "../../helpers/dom";
import { getObjectKeysAndSort } from "../../helpers/object";
import { isTouchSupported } from "../../helpers/touchSupport";
-import { logger, LogLevels } from "../../lib/logger";
+import { logger } from "../../lib/logger";
import rootScope from "../../lib/rootScope";
import AppMediaViewer from "../appMediaViewer";
import BubbleGroups from "../bubbleGroups";
-import Button from "../button";
import PopupDatePicker from "../popupDatepicker";
import PopupForward from "../popupForward";
import PopupStickers from "../popupStickers";
import ProgressivePreloader from "../preloader";
import Scrollable from "../scrollable";
import StickyIntersector from "../stickyIntersector";
-import ChatContextMenu from "./contextMenu";
-import ChatSelection from "./selection";
import animationIntersector from "../animationIntersector";
import { months } from "../../helpers/date";
import RichTextProcessor from "../../lib/richtextprocessor";
@@ -50,8 +47,6 @@ let TEST_SCROLL = TEST_SCROLL_TIMES;
export default class ChatBubbles {
bubblesContainer: HTMLDivElement;
chatInner: HTMLDivElement;
- goDownBtn: HTMLButtonElement;
-
scrollable: Scrollable;
scroll: HTMLElement;
@@ -59,6 +54,7 @@ export default class ChatBubbles {
private getHistoryBottomPromise: Promise;
public peerID = 0;
+ //public messagesCount: number = -1;
public unreadOut = new Set();
public needUpdate: {replyMid: number, mid: number}[] = []; // if need wrapSingleMessage
@@ -104,6 +100,8 @@ export default class ChatBubbles {
public listenerSetter: ListenerSetter;
+ public replyFollowHistory: number[] = [];
+
constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appSidebarRight: AppSidebarRight, private appStickersManager: AppStickersManager, private appUsersManager: AppUsersManager, private appInlineBotsManager: AppInlineBotsManager, private appPhotosManager: AppPhotosManager, private appDocsManager: AppDocsManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager) {
this.chat.log.error('Bubbles construction');
@@ -115,9 +113,7 @@ export default class ChatBubbles {
this.chatInner = document.createElement('div');
this.chatInner.classList.add('bubbles-inner');
- this.goDownBtn = Button('bubbles-go-down btn-corner z-depth-1 hide', {icon: 'down'});
-
- this.bubblesContainer.append(this.chatInner, this.goDownBtn);
+ this.bubblesContainer.append(this.chatInner);
this.setScroll();
@@ -209,7 +205,7 @@ export default class ChatBubbles {
// set new mids to album items for mediaViewer
if(message.grouped_id) {
- const items = bubble.querySelectorAll('.album-item');
+ const items = bubble.querySelectorAll('.grouped-item');
const groupIDs = getObjectKeysAndSort(appMessagesManager.groupedMessagesStorage[message.grouped_id]);
(Array.from(items) as HTMLElement[]).forEach((item, idx) => {
item.dataset.mid = '' + groupIDs[idx];
@@ -315,10 +311,25 @@ export default class ChatBubbles {
const info = e.detail;
const dialog = appMessagesManager.getDialogByPeerID(info.peerID)[0];
- if(dialog) {
- if(dialog.peerID == this.peerID) {
- this.updateUnreadByDialog(dialog);
- }
+ if(dialog?.peerID == this.peerID) {
+ this.chat.input.setUnreadCount();
+ this.updateUnreadByDialog(dialog);
+ }
+ });
+
+ this.listenerSetter.add(rootScope, 'dialogs_multiupdate', (e) => {
+ const dialogs = e.detail;
+
+ if(dialogs[this.peerID]) {
+ this.chat.input.setUnreadCount();
+ }
+ });
+
+ this.listenerSetter.add(rootScope, 'dialog_notify_settings', (e) => {
+ const peerID = e.detail;
+
+ if(this.peerID == peerID) {
+ this.chat.input.setUnreadCount();
}
});
@@ -362,7 +373,7 @@ export default class ChatBubbles {
}
//this.chatSelection.toggleByBubble(bubble);
- this.chat.selection.toggleByBubble(findUpClassName(target, 'album-item') || bubble);
+ this.chat.selection.toggleByBubble(findUpClassName(target, 'grouped-item') || bubble);
return;
}
@@ -494,6 +505,7 @@ export default class ChatBubbles {
} catch(err) {}
if(isReplyClick && bubble.classList.contains('is-reply')/* || bubble.classList.contains('forwarded') */) {
+ this.replyFollowHistory.push(+bubble.dataset.mid);
let originalMessageID = +bubble.getAttribute('data-original-mid');
this.chat.setPeer(this.peerID, originalMessageID);
}
@@ -507,18 +519,6 @@ export default class ChatBubbles {
//console.log('chatInner click', e);
}, {capture: true, passive: false});
-
- this.listenerSetter.add(this.goDownBtn, CLICK_EVENT_NAME, (e) => {
- cancelEvent(e);
- const dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0];
-
- if(dialog) {
- this.chat.setPeer(this.peerID/* , dialog.top_message */);
- } else {
- this.log('will scroll down 3');
- this.scroll.scrollTop = this.scroll.scrollHeight;
- }
- });
this.stickyIntersector = new StickyIntersector(this.scrollable.container, (stuck, target) => {
for(const timestamp in this.dateMessages) {
@@ -574,7 +574,43 @@ export default class ChatBubbles {
});
}
- public getAlbumBubble(groupID: string) {
+ public onGoDownClick() {
+ if(this.replyFollowHistory.length) {
+ this.replyFollowHistory.forEachReverse((mid, idx) => {
+ const bubble = this.bubbles[mid];
+ let bad = true;
+ if(bubble) {
+ const rect = bubble.getBoundingClientRect();
+ bad = (this.appPhotosManager.windowH / 2) > rect.top;
+ } else {
+ const message = this.appMessagesManager.getMessage(mid);
+ if(!message.deleted) {
+ bad = false;
+ }
+ }
+
+ if(bad) {
+ this.replyFollowHistory.splice(idx, 1);
+ }
+ });
+
+ this.replyFollowHistory.sort((a, b) => b - a);
+
+ const mid = this.replyFollowHistory.pop();
+ this.chat.setPeer(this.peerID, mid);
+ } else {
+ const dialog = this.appMessagesManager.getDialogByPeerID(this.peerID)[0];
+
+ if(dialog) {
+ this.chat.setPeer(this.peerID/* , dialog.top_message */);
+ } else {
+ this.log('will scroll down 3');
+ this.scroll.scrollTop = this.scroll.scrollHeight;
+ }
+ }
+ }
+
+ public getGroupedBubble(groupID: string) {
const group = this.appMessagesManager.groupedMessagesStorage[groupID];
for(const mid in group) {
if(this.bubbles[mid]) {
@@ -588,18 +624,22 @@ export default class ChatBubbles {
return null;
}
- public getBubbleAlbumItems(bubble: HTMLElement) {
- return Array.from(bubble.querySelectorAll('.album-item')) as HTMLElement[];
+ public getBubbleGroupedItems(bubble: HTMLElement) {
+ return Array.from(bubble.querySelectorAll('.grouped-item')) as HTMLElement[];
}
public getMountedBubble(mid: number) {
const message = this.appMessagesManager.getMessage(mid);
- const bubble = this.bubbles[mid];
- if(!bubble && message.grouped_id) {
- const a = this.getAlbumBubble(message.grouped_id);
- if(a) return a;
+ if(message.grouped_id) {
+ const a = this.getGroupedBubble(message.grouped_id);
+ if(a) {
+ a.bubble = a.bubble.querySelector(`.document-container[data-mid="${mid}"]`) || a.bubble;
+ return a;
+ }
}
+
+ const bubble = this.bubbles[mid];
if(!bubble) return;
return {bubble, message};
@@ -634,7 +674,7 @@ export default class ChatBubbles {
let dialog = this.appMessagesManager.getDialogByPeerID(this.peerID)[0];
// if scroll down after search
- if(!top && (!dialog || history.indexOf(dialog.top_message) === -1)) {
+ if(!top && (!dialog || history.indexOf(dialog.top_message) === -1)/* && this.chat.type == 'chat' */) {
this.log('Will load more (down) history by maxID:', history[history.length - 1], history);
/* false && */this.getHistory(history[history.length - 1], false, true, undefined, justLoad);
}
@@ -666,11 +706,13 @@ export default class ChatBubbles {
this.scrolledDown = false;
}
- this.chat.topbar.pinnedMessage.setCorrectIndex(this.scrollable.lastScrollDirection);
+ if(this.chat.topbar.pinnedMessage) {
+ this.chat.topbar.pinnedMessage.setCorrectIndex(this.scrollable.lastScrollDirection);
+ }
};
public setScroll() {
- this.scrollable = new Scrollable(this.bubblesContainer/* .firstElementChild */ as HTMLElement, 'IM', 300);
+ this.scrollable = new Scrollable(this.bubblesContainer/* .firstElementChild */ as HTMLElement, 'IM', /* 10300 */300);
/* const getScrollOffset = () => {
//return Math.round(Math.max(300, appPhotosManager.windowH / 1.5));
@@ -684,8 +726,6 @@ export default class ChatBubbles {
this.scrollable = new Scrollable(this.bubblesContainer, 'y', 'IM', this.chatInner, getScrollOffset()); */
this.scroll = this.scrollable.container;
- this.bubblesContainer/* .firstElementChild */.append(this.goDownBtn);
-
this.scrollable.onAdditionalScroll = this.onScroll;
this.scrollable.onScrolledTop = () => this.loadMoreHistory(true);
this.scrollable.onScrolledBottom = () => this.loadMoreHistory(false);
@@ -923,7 +963,6 @@ export default class ChatBubbles {
//console.time('appImManager setPeer pre promise');
////console.time('appImManager: pre render start');
if(peerID == 0) {
- this.goDownBtn.classList.add('hide');
this.cleanup(true);
this.peerID = 0;
return null;
@@ -932,14 +971,9 @@ export default class ChatBubbles {
const samePeer = this.peerID == peerID;
const dialog = this.appMessagesManager.getDialogByPeerID(peerID)[0] || null;
- let topMessage = lastMsgID <= 0 ? lastMsgID : dialog?.top_message ?? 0; // убрать + 1 после создания базы референсов
+ let topMessage = lastMsgID <= 0 ? lastMsgID : dialog?.top_message ?? 0;
const isTarget = lastMsgID !== undefined;
- // @ts-ignore
- /* if(topMessage && dialog && dialog.top_message == topMessage && dialog.refetchTopMessage) {
- // @ts-ignore
- dialog.refetchTopMessage = false;
- topMessage += 1;
- } */
+
if(!isTarget && dialog) {
if(dialog.unread_count && !samePeer) {
lastMsgID = dialog.read_inbox_max_id;
@@ -948,6 +982,8 @@ export default class ChatBubbles {
//lastMsgID = topMessage;
}
}
+
+ const isJump = lastMsgID != topMessage;
if(samePeer) {
const mounted = this.getMountedBubble(lastMsgID);
@@ -955,20 +991,22 @@ export default class ChatBubbles {
if(isTarget) {
this.scrollable.scrollIntoView(mounted.bubble);
this.highlightBubble(mounted.bubble);
- } else if(dialog && lastMsgID == topMessage) {
+ this.chat.setListenerResult('setPeer', lastMsgID, false);
+ } else if(dialog && !isJump) {
//this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight);
this.scroll.scrollTop = this.scroll.scrollHeight;
+ this.chat.setListenerResult('setPeer', lastMsgID, true);
}
return null;
}
} else {
this.peerID = peerID;
+ this.replyFollowHistory.length = 0;
}
this.log('setPeer peerID:', this.peerID, dialog, lastMsgID, topMessage);
- const isJump = lastMsgID != topMessage;
// add last message, bc in getHistory will load < max_id
const additionMsgID = isJump ? 0 : topMessage;
@@ -978,7 +1016,21 @@ export default class ChatBubbles {
//////appSidebarRight.toggleSidebar(true);
- const maxBubbleID = samePeer && Math.max(...Object.keys(this.bubbles).map(mid => +mid));
+ let maxBubbleID = 0;
+ if(samePeer) {
+ let el = getElementByPoint(this.chat.bubbles.scrollable.container, 'bottom');
+ //this.chat.log('[PM]: setCorrectIndex: get last element perf:', performance.now() - perf, el);
+ if(el) {
+ el = findUpClassName(el, 'bubble');
+ if(el) { // TODO: а что делать, если id будет -1, -2, -3?
+ maxBubbleID = +el.dataset.mid;
+ }
+ }
+
+ if(maxBubbleID <= 0) {
+ maxBubbleID = Math.max(...Object.keys(this.bubbles).map(mid => +mid));
+ }
+ }
const oldChatInner = this.chatInner;
this.cleanup();
@@ -999,6 +1051,10 @@ export default class ChatBubbles {
this.scrollable.container.innerHTML = '';
//oldChatInner.remove();
+ if(!samePeer) {
+ this.chat.finishPeerChange(isTarget, isJump, lastMsgID);
+ }
+
this.preloader.attach(this.bubblesContainer);
}
@@ -1009,6 +1065,10 @@ export default class ChatBubbles {
////this.log('setPeer removing preloader');
if(cached) {
+ if(!samePeer) {
+ this.chat.finishPeerChange(isTarget, isJump, lastMsgID); // * костыль
+ }
+
this.scrollable.container.innerHTML = '';
//oldChatInner.remove();
} else {
@@ -1024,7 +1084,7 @@ export default class ChatBubbles {
this.lazyLoadQueue.unlock();
//if(dialog && lastMsgID && lastMsgID != topMessage && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) {
- if(dialog && (isTarget || (lastMsgID != topMessage)) && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) {
+ if(dialog && (isTarget || isJump)) {
if(this.scrollable.scrollLocked) {
clearTimeout(this.scrollable.scrollLocked);
this.scrollable.scrollLocked = 0;
@@ -1034,9 +1094,12 @@ export default class ChatBubbles {
const forwardingUnread = dialog.read_inbox_max_id == lastMsgID && !isTarget;
if(!fromUp && (samePeer || forwardingUnread)) {
this.scrollable.scrollTop = this.scrollable.scrollHeight;
+ } else if(fromUp/* && (samePeer || forwardingUnread) */) {
+ this.scrollable.scrollTop = 0;
}
- let bubble: HTMLElement = forwardingUnread ? (this.firstUnreadBubble || this.bubbles[lastMsgID]) : this.bubbles[lastMsgID];
+ const mountedByLastMsgID = this.getMountedBubble(lastMsgID);
+ let bubble: HTMLElement = (forwardingUnread && this.firstUnreadBubble) || mountedByLastMsgID?.bubble;
if(!bubble?.parentElement) {
bubble = this.findNextMountedBubbleByMsgID(lastMsgID);
}
@@ -1049,6 +1112,8 @@ export default class ChatBubbles {
this.scrollable.scrollTop = this.scrollable.scrollHeight;
}
+ this.chat.setListenerResult('setPeer', lastMsgID, !isJump);
+
// warning
if(!lastMsgID || this.bubbles[topMessage] || lastMsgID == topMessage) {
this.scrolledAllDown = true;
@@ -1084,7 +1149,6 @@ export default class ChatBubbles {
const isAnyGroup = this.appPeersManager.isAnyGroup(peerID);
const isChannel = this.appPeersManager.isChannel(peerID);
- const isBroadcast = this.appPeersManager.isBroadcast(peerID);
const canWrite = this.appMessagesManager.canWriteToPeer(peerID);
@@ -1093,11 +1157,6 @@ export default class ChatBubbles {
this.chatInner.classList.toggle('is-chat', isAnyGroup || peerID == rootScope.myID);
this.chatInner.classList.toggle('is-channel', isChannel);
- this.goDownBtn.classList.toggle('is-broadcast', isBroadcast);
-
- window.requestAnimationFrame(() => {
- this.goDownBtn.classList.remove('hide');
- });
}
public renderMessagesQueue(message: any, bubble: HTMLDivElement, reverse: boolean) {
@@ -1205,8 +1264,9 @@ export default class ChatBubbles {
public renderMessage(message: any, reverse = false, multipleRender = false, bubble: HTMLDivElement = null, updatePosition = true) {
this.log.debug('message to render:', message);
//return;
+ const albumMustBeRenderedFull = this.chat.type == 'chat';
if(message.deleted) return;
- else if(message.grouped_id) { // will render only last album's message
+ else if(message.grouped_id && albumMustBeRenderedFull) { // will render only last album's message
const storage = this.appMessagesManager.groupedMessagesStorage[message.grouped_id];
const maxID = Math.max(...Object.keys(storage).map(i => +i));
if(message.mid < maxID) {
@@ -1215,7 +1275,7 @@ export default class ChatBubbles {
}
const peerID = this.peerID;
- const our = message.fromID == rootScope.myID;
+ const our = message.fromID == rootScope.myID; // * can't use 'message.pFlags.out' here because this check will be used to define side of message (left-right)
const messageDiv = document.createElement('div');
messageDiv.classList.add('message');
@@ -1233,7 +1293,7 @@ export default class ChatBubbles {
bubble.classList.add('bubble');
bubble.appendChild(bubbleContainer);
- if(!our) {
+ if(!our && !message.pFlags.out) {
//this.log('not our message', message, message.pFlags.unread);
if(message.pFlags.unread) {
this.unreadedObserver.observe(bubble);
@@ -1300,7 +1360,9 @@ export default class ChatBubbles {
let messageMedia = message.media;
let messageMessage: string, totalEntities: any[];
- if(message.grouped_id) {
+ if(messageMedia?.document && !messageMedia.document.type) {
+ // * just filter this case
+ } else if(message.grouped_id && albumMustBeRenderedFull) {
const t = this.appMessagesManager.getAlbumText(message.grouped_id);
messageMessage = t.message;
totalEntities = t.totalEntities;
@@ -1436,6 +1498,7 @@ export default class ChatBubbles {
}
const isOut = our && (!message.fwd_from || this.peerID != rootScope.myID);
+ let nameContainer = bubbleContainer;
// media
if(messageMedia/* && messageMedia._ == 'messageMediaPhoto' */) {
@@ -1457,7 +1520,7 @@ export default class ChatBubbles {
case 'album': {
this.log('will wrap pending album');
- bubble.classList.add('hide-name', 'photo', 'is-album');
+ bubble.classList.add('hide-name', 'photo', 'is-album', 'is-grouped');
wrapAlbum({
groupID: '' + message.id,
attachmentDiv,
@@ -1548,8 +1611,8 @@ export default class ChatBubbles {
const tailSupported = !isAndroid;
const storage = this.appMessagesManager.groupedMessagesStorage[message.grouped_id];
- if(message.grouped_id && Object.keys(storage).length != 1) {
- bubble.classList.add('is-album');
+ if(message.grouped_id && Object.keys(storage).length != 1 && albumMustBeRenderedFull) {
+ bubble.classList.add('is-album', 'is-grouped');
wrapAlbum({
groupID: message.grouped_id,
attachmentDiv,
@@ -1713,8 +1776,8 @@ export default class ChatBubbles {
bubble.classList.add('hide-name', doc.type == 'round' ? 'round' : 'video');
const storage = this.appMessagesManager.groupedMessagesStorage[message.grouped_id];
- if(message.grouped_id && Object.keys(storage).length != 1) {
- bubble.classList.add('is-album');
+ if(message.grouped_id && Object.keys(storage).length != 1 && albumMustBeRenderedFull) {
+ bubble.classList.add('is-album', 'is-grouped');
wrapAlbum({
groupID: message.grouped_id,
@@ -1742,10 +1805,55 @@ export default class ChatBubbles {
break;
} else {
- const docDiv = wrapDocument(doc, false, false, message.mid);
-
+ //const storage = this.appMessagesManager.groupedMessagesStorage[message.grouped_id];
+ //const isFullAlbum = storage && Object.keys(storage).length != 1;
+ const mids = albumMustBeRenderedFull ? this.appMessagesManager.getMidsByMid(message.mid) : [message.mid];
+ mids.forEach((mid, idx) => {
+ const message = this.appMessagesManager.getMessage(mid);
+ const doc = message.media.document;
+ const div = wrapDocument(doc, false, false, mid);
+
+ const container = document.createElement('div');
+ container.classList.add('document-container');
+ container.dataset.mid = '' + mid;
+
+ const wrapper = document.createElement('div');
+ wrapper.classList.add('document-wrapper');
+
+ if(message.message) {
+ const messageDiv = document.createElement('div');
+ messageDiv.classList.add('document-message');
+
+ const richText = RichTextProcessor.wrapRichText(message.message, {
+ entities: message.totalEntities
+ });
+
+ messageDiv.innerHTML = richText;
+ wrapper.append(messageDiv);
+ }
+
+ if(mids.length > 1) {
+ const selection = document.createElement('div');
+ selection.classList.add('document-selection');
+ container.append(selection);
+
+ container.classList.add('grouped-item');
+
+ if(idx === 0) {
+ nameContainer = wrapper;
+ }
+ }
+
+ wrapper.append(div);
+ container.append(wrapper);
+ messageDiv.append(container);
+ });
+
+ if(mids.length > 1) {
+ bubble.classList.add('is-multiple-documents', 'is-grouped');
+ }
+
bubble.classList.remove('is-message-empty');
- messageDiv.append(docDiv);
messageDiv.classList.add((doc.type != 'photo' ? doc.type || 'document' : 'document') + '-message');
processingWebPage = true;
@@ -1818,6 +1926,8 @@ export default class ChatBubbles {
}
} */
}
+
+ let savedFrom = '';
if((this.peerID < 0 && !our) || message.fwd_from || message.reply_to_mid) { // chat
let title = this.appPeersManager.getPeerTitle(message.fwdFromID || message.fromID);
@@ -1840,11 +1950,7 @@ export default class ChatBubbles {
}
if(message.savedFrom) {
- let goto = document.createElement('div');
- goto.classList.add('bubble-beside-button', 'goto-original', 'tgico-next');
- bubbleContainer.append(goto);
- bubble.dataset.savedFrom = message.savedFrom;
- bubble.classList.add('with-beside-button');
+ savedFrom = message.savedFrom;
}
if(!bubble.classList.contains('sticker')) {
@@ -1861,7 +1967,7 @@ export default class ChatBubbles {
nameDiv.innerHTML = 'Forwarded from ' + title;
}
- bubbleContainer.append(nameDiv);
+ nameContainer.append(nameDiv);
}
} else {
if(message.reply_to_mid) {
@@ -1895,7 +2001,7 @@ export default class ChatBubbles {
nameDiv.innerHTML = title;
nameDiv.style.color = this.appPeersManager.getPeerColorByID(message.fromID, false);
nameDiv.dataset.peerID = message.fromID;
- bubbleContainer.append(nameDiv);
+ nameContainer.append(nameDiv);
} else /* if(!message.reply_to_mid) */ {
bubble.classList.add('hide-name');
}
@@ -1920,6 +2026,18 @@ export default class ChatBubbles {
} else {
bubble.classList.add('hide-name');
}
+
+ if(this.chat.type == 'pinned') {
+ savedFrom = `${this.chat.peerID}_${message.mid}`;
+ }
+
+ if(savedFrom) {
+ const goto = document.createElement('div');
+ goto.classList.add('bubble-beside-button', 'goto-original', 'tgico-next');
+ bubbleContainer.append(goto);
+ bubble.dataset.savedFrom = savedFrom;
+ bubble.classList.add('with-beside-button');
+ }
bubble.classList.add(isOut ? 'is-out' : 'is-in');
if(updatePosition) {
@@ -1977,13 +2095,14 @@ export default class ChatBubbles {
const method = (reverse ? history.shift : history.pop).bind(history);
- //const padding = 99999;
+ //const padding = 10000;
const realLength = this.scrollable.container.childElementCount;
let previousScrollHeightMinusTop: number/* , previousScrollHeight: number */;
if(realLength > 0 && (reverse || isSafari)) { // for safari need set when scrolling bottom too
this.messagesQueueOnRender = () => {
const {scrollTop, scrollHeight} = this.scrollable;
+ //previousScrollHeight = scrollHeight;
//previousScrollHeight = scrollHeight + padding;
previousScrollHeightMinusTop = reverse ? scrollHeight - scrollTop : scrollTop;
@@ -2011,6 +2130,10 @@ export default class ChatBubbles {
/* const scrollHeight = this.scrollable.scrollHeight;
const addedHeight = scrollHeight - previousScrollHeight;
+ this.chatInner.style.paddingTop = (10000 - addedHeight) + 'px'; */
+ /* const scrollHeight = this.scrollable.scrollHeight;
+ const addedHeight = scrollHeight - previousScrollHeight;
+
this.chatInner.style.paddingTop = (padding - addedHeight) + 'px';
//const newScrollTop = reverse ? scrollHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop;
@@ -2054,6 +2177,26 @@ export default class ChatBubbles {
});
};
+ public requestHistory(maxID: number, loadCount: number, backLimit: number) {
+ //const middleware = this.getMiddleware();
+ if(this.chat.type == 'chat') {
+ return this.appMessagesManager.getHistory(this.peerID, maxID, loadCount, backLimit);
+ } else if(this.chat.type == 'pinned') {
+ const promise = this.appMessagesManager.getSearch(this.peerID, '', {_: 'inputMessagesFilterPinned'}, maxID, loadCount, 0, backLimit);
+
+ /* if(maxID) {
+ promise.then(result => {
+ if(!middleware()) return;
+
+ this.messagesCount = result.count;
+ this.chat.topbar.setTitle();
+ });
+ } */
+
+ return promise;
+ }
+ }
+
/**
* Load and render history
* @param maxID max message id
@@ -2099,7 +2242,7 @@ export default class ChatBubbles {
}
let additionMsgIDs: number[];
- if(additionMsgID) {
+ if(additionMsgID && !isBackLimit) {
const historyStorage = this.appMessagesManager.historiesStorage[peerID];
if(historyStorage && historyStorage.history.length < loadCount) {
additionMsgIDs = historyStorage.history.slice();
@@ -2118,7 +2261,7 @@ export default class ChatBubbles {
/* const result = additionMsgID ?
{history: [additionMsgID]} :
appMessagesManager.getHistory(this.peerID, maxID, loadCount, backLimit); */
- let result: ReturnType | {history: number[]} = this.appMessagesManager.getHistory(this.peerID, maxID, loadCount, backLimit);
+ let result: ReturnType | {history: number[]} = this.requestHistory(maxID, loadCount, backLimit) as any;
let resultPromise: Promise;
//const isFirstMessageRender = !!additionMsgID && result instanceof Promise && !appMessagesManager.getMessage(additionMsgID).grouped_id;
@@ -2251,10 +2394,19 @@ export default class ChatBubbles {
// preload more
//if(!isFirstMessageRender) {
- setTimeout(() => {
- this.loadMoreHistory(true, true);
- this.loadMoreHistory(false, true);
- }, 0);
+ if(this.chat.type == 'chat') {
+ const storage = this.appMessagesManager.historiesStorage[peerID];
+ const isMaxIDInHistory = storage.history.indexOf(maxID) !== -1;
+ if(isMaxIDInHistory) { // * otherwise it is a search or jump
+ setTimeout(() => {
+ if(reverse) {
+ this.loadMoreHistory(true, true);
+ } else {
+ this.loadMoreHistory(false, true);
+ }
+ }, 0);
+ }
+ }
//}
});
diff --git a/src/components/chat/chat.ts b/src/components/chat/chat.ts
index 13d81d0e..4dd3f5ad 100644
--- a/src/components/chat/chat.ts
+++ b/src/components/chat/chat.ts
@@ -10,6 +10,7 @@ import type { AppProfileManager } from "../../lib/appManagers/appProfileManager"
import type { AppStickersManager } from "../../lib/appManagers/appStickersManager";
import type { AppUsersManager } from "../../lib/appManagers/appUsersManager";
import type { AppWebPagesManager } from "../../lib/appManagers/appWebPagesManager";
+import EventListenerBase from "../../helpers/eventListenerBase";
import { logger, LogLevels } from "../../lib/logger";
import rootScope from "../../lib/rootScope";
import appSidebarRight, { AppSidebarRight } from "../sidebarRight";
@@ -19,7 +20,11 @@ import ChatInput from "./input";
import ChatSelection from "./selection";
import ChatTopbar from "./topbar";
-export default class Chat {
+export type ChatType = 'chat' | 'pinned' | 'replies' | 'discussion';
+
+export default class Chat extends EventListenerBase<{
+ setPeer: (mid: number, isTopMessage: boolean) => void
+}> {
public container: HTMLElement;
public backgroundEl: HTMLElement;
@@ -33,9 +38,13 @@ export default class Chat {
public setPeerPromise: Promise;
public peerChanged: boolean;
- public log: ReturnType;
+ public log: ReturnType;
+
+ public type: ChatType = 'chat';
constructor(public appImManager: AppImManager, private appChatsManager: AppChatsManager, private appDocsManager: AppDocsManager, private appInlineBotsManager: AppInlineBotsManager, private appMessagesManager: AppMessagesManager, private appPeersManager: AppPeersManager, private appPhotosManager: AppPhotosManager, private appProfileManager: AppProfileManager, private appStickersManager: AppStickersManager, private appUsersManager: AppUsersManager, private appWebPagesManager: AppWebPagesManager, private appSidebarRight: AppSidebarRight, private appPollsManager: AppPollsManager) {
+ super();
+
this.container = document.createElement('div');
this.container.classList.add('chat');
@@ -52,12 +61,25 @@ export default class Chat {
}
private init() {
- this.topbar = new ChatTopbar(this, appSidebarRight, this.appMessagesManager, this.appPeersManager, this.appChatsManager, this.appUsersManager, this.appProfileManager);
+ this.topbar = new ChatTopbar(this, appSidebarRight, this.appMessagesManager, this.appPeersManager, this.appChatsManager);
this.bubbles = new ChatBubbles(this, this.appMessagesManager, this.appSidebarRight, this.appStickersManager, this.appUsersManager, this.appInlineBotsManager, this.appPhotosManager, this.appDocsManager, this.appPeersManager, this.appChatsManager);
this.input = new ChatInput(this, this.appMessagesManager, this.appDocsManager, this.appChatsManager, this.appPeersManager, this.appWebPagesManager, this.appImManager);
this.selection = new ChatSelection(this.bubbles, this.input, this.appMessagesManager);
this.contextMenu = new ChatContextMenu(this.bubbles.bubblesContainer, this, this.appMessagesManager, this.appChatsManager, this.appPeersManager, this.appPollsManager);
+ if(this.type == 'chat') {
+ this.topbar.constructPeerHelpers();
+ }
+
+ this.topbar.construct();
+ this.input.construct();
+
+ if(this.type == 'chat') { // * гений в деле, разный порядок из-за разной последовательности действий
+ this.input.constructPeerHelpers();
+ } else if(this.type == 'pinned') {
+ this.input.constructPinnedHelpers();
+ }
+
this.container.append(this.topbar.container, this.bubbles.bubblesContainer, this.input.chatInput);
}
@@ -126,24 +148,11 @@ export default class Chat {
return;
}
- const {cached, promise} = result;
+ const {promise} = result;
- // clear
- if(!cached) {
- if(!samePeer) {
- this.finishPeerChange();
- }
- }
-
//console.timeEnd('appImManager setPeer pre promise');
- this.setPeerPromise = promise.then(() => {
- if(cached) {
- if(!samePeer) {
- this.finishPeerChange();
- }
- }
- }).finally(() => {
+ this.setPeerPromise = promise.finally(() => {
if(this.peerID == peerID) {
this.setPeerPromise = null;
}
@@ -155,18 +164,19 @@ export default class Chat {
return this.setPeerPromise;
}
- public finishPeerChange() {
+ public finishPeerChange(isTarget: boolean, isJump: boolean, lastMsgID: number) {
if(this.peerChanged) return;
let peerID = this.peerID;
this.peerChanged = true;
this.topbar.setPeer(peerID);
+ this.topbar.finishPeerChange(isTarget, isJump, lastMsgID);
this.bubbles.finishPeerChange();
this.input.finishPeerChange();
appSidebarRight.sharedMediaTab.fillProfileElements();
- rootScope.broadcast('peer_changed', this.peerID);
+ rootScope.broadcast('peer_changed', peerID);
}
}
\ No newline at end of file
diff --git a/src/components/chat/contextMenu.ts b/src/components/chat/contextMenu.ts
index 223e2f4c..692ad4bb 100644
--- a/src/components/chat/contextMenu.ts
+++ b/src/components/chat/contextMenu.ts
@@ -4,7 +4,6 @@ import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
import type { AppPollsManager, Poll } from "../../lib/appManagers/appPollsManager";
import type Chat from "./chat";
import { isTouchSupported } from "../../helpers/touchSupport";
-import rootScope from "../../lib/rootScope";
import { attachClickEvent, cancelEvent, cancelSelection, findUpClassName } from "../../helpers/dom";
import ButtonMenu, { ButtonMenuItemOptions } from "../buttonMenu";
import { attachContextMenuListener, openBtnMenu, positionMenu } from "../misc";
@@ -18,7 +17,7 @@ export default class ChatContextMenu {
private element: HTMLElement;
private target: HTMLElement;
- private isTargetAnAlbumItem: boolean;
+ private isTargetAGroupedItem: boolean;
public peerID: number;
public msgID: number;
@@ -63,10 +62,10 @@ export default class ChatContextMenu {
//this.msgID = msgID;
this.target = e.target as HTMLElement;
- const albumItem = findUpClassName(this.target, 'album-item');
- this.isTargetAnAlbumItem = !!albumItem;
- if(albumItem) {
- this.msgID = +albumItem.dataset.mid;
+ const groupedItem = findUpClassName(this.target, 'grouped-item');
+ this.isTargetAGroupedItem = !!groupedItem;
+ if(groupedItem) {
+ this.msgID = +groupedItem.dataset.mid;
} else {
this.msgID = mid;
}
@@ -125,7 +124,7 @@ export default class ChatContextMenu {
cancelSelection();
//cancelEvent(e as any);
- const bubble = findUpClassName(e.target, 'album-item') || findUpClassName(e.target, 'bubble');
+ const bubble = findUpClassName(e.target, 'grouped-item') || findUpClassName(e.target, 'bubble');
if(bubble) {
chat.selection.toggleByBubble(bubble);
}
@@ -138,13 +137,13 @@ export default class ChatContextMenu {
icon: 'reply',
text: 'Reply',
onClick: this.onReplyClick,
- verify: () => (this.peerID > 0 || this.appChatsManager.hasRights(-this.peerID, 'send')) && this.msgID > 0/* ,
+ verify: () => (this.peerID > 0 || this.appChatsManager.hasRights(-this.peerID, 'send')) && this.msgID > 0 && !!this.chat.input.messageInput/* ,
cancelEvent: true */
}, {
icon: 'edit',
text: 'Edit',
onClick: this.onEditClick,
- verify: () => this.appMessagesManager.canEditMessage(this.msgID, 'text')
+ verify: () => this.appMessagesManager.canEditMessage(this.msgID, 'text') && !!this.chat.input.messageInput
}, {
icon: 'copy',
text: 'Copy',
@@ -163,15 +162,16 @@ export default class ChatContextMenu {
onClick: this.onPinClick,
verify: () => {
const message = this.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 && this.appChatsManager.hasRights(-this.peerID, 'pin')));
+ return this.msgID > 0 && message._ != 'messageService' && !message.pFlags.pinned && this.appPeersManager.canPinMessage(this.peerID);
}
}, {
icon: 'unpin',
text: 'Unpin',
onClick: this.onUnpinClick,
- verify: () => /* appImManager.pinnedMsgID == this.msgID && */ this.appPeersManager.canPinMessage(this.peerID)
+ verify: () => {
+ const message = this.appMessagesManager.getMessage(this.msgID);
+ return message.pFlags.pinned && this.appPeersManager.canPinMessage(this.peerID);
+ }
}, {
icon: 'revote',
text: 'Revote',
@@ -284,12 +284,12 @@ export default class ChatContextMenu {
if(this.chat.selection.isSelecting) {
this.chat.selection.selectionForwardBtn.click();
} else {
- new PopupForward(this.isTargetAnAlbumItem ? [this.msgID] : this.appMessagesManager.getMidsByMid(this.msgID));
+ new PopupForward(this.isTargetAGroupedItem ? [this.msgID] : this.appMessagesManager.getMidsByMid(this.msgID));
}
};
private onSelectClick = () => {
- this.chat.selection.toggleByBubble(findUpClassName(this.target, 'album-item') || findUpClassName(this.target, 'bubble'));
+ this.chat.selection.toggleByBubble(findUpClassName(this.target, 'grouped-item') || findUpClassName(this.target, 'bubble'));
};
private onClearSelectionClick = () => {
@@ -300,7 +300,7 @@ export default class ChatContextMenu {
if(this.chat.selection.isSelecting) {
this.chat.selection.selectionDeleteBtn.click();
} else {
- new PopupDeleteMessages(this.isTargetAnAlbumItem ? [this.msgID] : this.appMessagesManager.getMidsByMid(this.msgID));
+ new PopupDeleteMessages(this.isTargetAGroupedItem ? [this.msgID] : this.appMessagesManager.getMidsByMid(this.msgID));
}
};
}
\ No newline at end of file
diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts
index b2a9ce27..01d717fa 100644
--- a/src/components/chat/input.ts
+++ b/src/components/chat/input.ts
@@ -22,12 +22,12 @@ import { toast } from "../toast";
import { wrapReply } from "../wrappers";
import InputField from '../inputField';
import { MessageEntity } from '../../layer';
-import MarkupTooltip from './markupTooltip';
import StickersHelper from './stickersHelper';
import ButtonIcon from '../buttonIcon';
import DivAndCaption from '../divAndCaption';
import ButtonMenuToggle from '../buttonMenuToggle';
import ListenerSetter from '../../helpers/listenerSetter';
+import Button from '../button';
const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@@ -94,11 +94,19 @@ export default class ChatInput {
public stickersHelper: StickersHelper;
public listenerSetter: ListenerSetter;
+ public pinnedControlBtn: HTMLButtonElement;
+
+ public goDownBtn: HTMLButtonElement;
+ public goDownUnreadBadge: HTMLElement;
+
constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appDocsManager: AppDocsManager, private appChatsManager: AppChatsManager, private appPeersManager: AppPeersManager, private appWebPagesManager: AppWebPagesManager, private appImManager: AppImManager) {
this.listenerSetter = new ListenerSetter();
+ }
+ public construct() {
this.chatInput = document.createElement('div');
this.chatInput.classList.add('chat-input');
+ this.chatInput.style.display = 'none';
this.inputContainer = document.createElement('div');
this.inputContainer.classList.add('chat-input-container');
@@ -106,6 +114,24 @@ export default class ChatInput {
this.rowsWrapper = document.createElement('div');
this.rowsWrapper.classList.add('rows-wrapper');
+ this.inputContainer.append(this.rowsWrapper);
+ this.chatInput.append(this.inputContainer);
+
+ this.goDownBtn = Button('bubbles-go-down btn-corner btn-circle z-depth-1 hide', {icon: 'arrow-down'});
+ this.goDownUnreadBadge = document.createElement('span');
+ this.goDownUnreadBadge.classList.add('badge', 'badge-24', 'badge-green');
+ this.goDownBtn.append(this.goDownUnreadBadge);
+ this.chatInput.append(this.goDownBtn);
+
+ this.listenerSetter.add(this.goDownBtn, CLICK_EVENT_NAME, (e) => {
+ cancelEvent(e);
+ this.chat.bubbles.onGoDownClick();
+ });
+
+ // * constructor end
+ }
+
+ public constructPeerHelpers() {
this.replyElements.container = document.createElement('div');
this.replyElements.container.classList.add('reply-wrapper');
@@ -137,7 +163,7 @@ export default class ChatInput {
this.willAttachType = 'media';
this.fileInput.click();
},
- verify: (peerID: number) => peerID > 0 || appChatsManager.hasRights(peerID, 'send', 'send_media')
+ verify: (peerID: number) => peerID > 0 || this.appChatsManager.hasRights(peerID, 'send', 'send_media')
}, {
icon: 'document',
text: 'Document',
@@ -147,14 +173,14 @@ export default class ChatInput {
this.willAttachType = 'document';
this.fileInput.click();
},
- verify: (peerID: number) => peerID > 0 || appChatsManager.hasRights(peerID, 'send', 'send_media')
+ verify: (peerID: number) => peerID > 0 || this.appChatsManager.hasRights(peerID, 'send', 'send_media')
}, {
icon: 'poll',
text: 'Poll',
onClick: () => {
new PopupCreatePoll(this.chat.peerID).show();
},
- verify: (peerID: number) => peerID < 0 && appChatsManager.hasRights(peerID, 'send', 'send_polls')
+ verify: (peerID: number) => peerID < 0 && this.appChatsManager.hasRights(peerID, 'send', 'send_polls')
}];
this.attachMenu = ButtonMenuToggle({noRipple: true, listenerSetter: this.listenerSetter}, 'top-left', this.attachMenuButtons);
@@ -189,12 +215,8 @@ export default class ChatInput {
this.btnSendContainer.append(this.recordRippleEl, this.btnSend);
- this.inputContainer.append(this.rowsWrapper, this.btnCancelRecord, this.btnSendContainer);
- this.chatInput.append(this.inputContainer);
+ this.inputContainer.append(this.btnCancelRecord, this.btnSendContainer);
- // * constructor end
-
- const toggleClass = isTouchSupported ? 'flip-icon' : 'active';
emoticonsDropdown.attachButtonListener(this.btnToggleEmoticons);
emoticonsDropdown.events.onOpen.push(this.onEmoticonsOpen);
emoticonsDropdown.events.onClose.push(this.onEmoticonsClose);
@@ -272,7 +294,7 @@ export default class ChatInput {
let peerID = this.chat.peerID;
// тут objectURL ставится уже с audio/wav
- appMessagesManager.sendFile(peerID, dataBlob, {
+ this.appMessagesManager.sendFile(peerID, dataBlob, {
isVoiceMessage: true,
isMedia: true,
duration,
@@ -290,6 +312,34 @@ export default class ChatInput {
this.listenerSetter.add(this.replyElements.container, CLICK_EVENT_NAME, this.onHelperClick);
}
+ public constructPinnedHelpers() {
+ const container = document.createElement('div');
+ container.classList.add('pinned-container');
+
+ this.pinnedControlBtn = Button('btn-primary btn-transparent pinned-container-button', {icon: 'unpin'});
+ container.append(this.pinnedControlBtn);
+
+ this.listenerSetter.add(this.pinnedControlBtn, 'click', () => {
+ const peerID = this.chat.peerID;
+
+ let promise: Promise;
+ if(this.appPeersManager.canPinMessage(peerID)) {
+ promise = this.appMessagesManager.unpinAllMessages(peerID);
+ } else {
+ promise = this.appMessagesManager.hidePinnedMessages(peerID);
+ }
+
+ promise.then(() => {
+ this.chat.appImManager.setPeer(0); // * close tab
+ });
+ });
+
+ this.rowsWrapper.append(container);
+
+ this.chatInput.classList.add('type-pinned');
+ this.rowsWrapper.classList.add('is-centered');
+ }
+
private onEmoticonsOpen = () => {
const toggleClass = isTouchSupported ? 'flip-icon' : 'active';
this.btnToggleEmoticons.classList.toggle(toggleClass, true);
@@ -300,6 +350,13 @@ export default class ChatInput {
this.btnToggleEmoticons.classList.toggle(toggleClass, false);
};
+ public setUnreadCount() {
+ const dialog = this.appMessagesManager.getDialogByPeerID(this.chat.peerID)[0];
+ const count = dialog?.unread_count;
+ this.goDownUnreadBadge.innerText = '' + (count || '');
+ this.goDownUnreadBadge.classList.toggle('badge-gray', this.appMessagesManager.isPeerMuted(this.chat.peerID));
+ }
+
public destroy() {
this.chat.log.error('Input destroying');
@@ -312,33 +369,54 @@ export default class ChatInput {
public cleanup() {
if(!this.chat.peerID) {
this.chatInput.style.display = 'none';
+ this.goDownBtn.classList.add('hide');
}
cancelSelection();
- this.clearInput();
- this.clearHelper();
+
+ if(this.messageInput) {
+ this.clearInput();
+ this.clearHelper();
+ }
}
public finishPeerChange() {
const peerID = this.chat.peerID;
- const visible = this.attachMenuButtons.filter(button => {
- const good = button.verify(peerID);
- button.element.classList.toggle('hide', !good);
- return good;
- });
-
- const canWrite = this.appMessagesManager.canWriteToPeer(peerID);
this.chatInput.style.display = '';
- this.chatInput.classList.toggle('is-hidden', !canWrite);
- if(!canWrite) {
- this.messageInput.removeAttribute('contenteditable');
- } else {
- this.messageInput.setAttribute('contenteditable', 'true');
+
+ const isBroadcast = this.appPeersManager.isBroadcast(peerID);
+ this.goDownBtn.classList.toggle('is-broadcast', isBroadcast);
+ this.goDownBtn.classList.remove('hide');
+
+ if(this.goDownUnreadBadge) {
+ this.setUnreadCount();
+ }
+
+ if(this.messageInput) {
+ const canWrite = this.appMessagesManager.canWriteToPeer(peerID);
+ this.chatInput.classList.add('no-transition');
+ this.chatInput.classList.toggle('is-hidden', !canWrite);
+ void this.chatInput.offsetLeft; // reflow
+ this.chatInput.classList.remove('no-transition');
+
+ const visible = this.attachMenuButtons.filter(button => {
+ const good = button.verify(peerID);
+ button.element.classList.toggle('hide', !good);
+ return good;
+ });
+
+ if(!canWrite) {
+ this.messageInput.removeAttribute('contenteditable');
+ } else {
+ this.messageInput.setAttribute('contenteditable', 'true');
+ }
+
+ this.attachMenu.toggleAttribute('disabled', !visible.length);
+ this.updateSendBtn();
+ } else if(this.pinnedControlBtn) {
+ this.pinnedControlBtn.append(this.appPeersManager.canPinMessage(this.chat.peerID) ? 'Unpin all messages' : 'Don\'t show pinned messages');
}
-
- this.attachMenu.toggleAttribute('disabled', !visible.length);
- this.updateSendBtn();
}
private attachMessageInputField() {
diff --git a/src/components/chat/messageRender.ts b/src/components/chat/messageRender.ts
index e14f3090..a550584f 100644
--- a/src/components/chat/messageRender.ts
+++ b/src/components/chat/messageRender.ts
@@ -1,5 +1,6 @@
import { getFullDate } from "../../helpers/date";
import { formatNumber } from "../../helpers/number";
+import appImManager from "../../lib/appManagers/appImManager";
import RichTextProcessor from "../../lib/richtextprocessor";
type Message = any;
@@ -39,7 +40,7 @@ export namespace MessageRender {
time = 'edited ' + time;
}
- if(message.pFlags.pinned) {
+ if(appImManager.chat.type != 'pinned' && message.pFlags.pinned) {
bubble.classList.add('is-pinned');
time = '' + time;
}
diff --git a/src/components/chat/pinnedContainer.ts b/src/components/chat/pinnedContainer.ts
index d102a321..362a3781 100644
--- a/src/components/chat/pinnedContainer.ts
+++ b/src/components/chat/pinnedContainer.ts
@@ -4,6 +4,7 @@ import mediaSizes from "../../helpers/mediaSizes";
import { cancelEvent } from "../../helpers/dom";
import DivAndCaption from "../divAndCaption";
import { ripple } from "../ripple";
+import ListenerSetter from "../../helpers/listenerSetter";
const classNames: string[] = [];
const CLASSNAME_BASE = 'pinned-container';
@@ -13,7 +14,7 @@ export default class PinnedContainer {
private close: HTMLElement;
protected wrapper: HTMLElement;
- constructor(protected topbar: ChatTopbar, protected chat: Chat, protected className: string, public divAndCaption: DivAndCaption<(title: string, subtitle: string, message?: any) => void>, onClose?: () => void | Promise) {
+ constructor(protected topbar: ChatTopbar, protected chat: Chat, public listenerSetter: ListenerSetter, 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;
@@ -39,7 +40,7 @@ export default class PinnedContainer {
divAndCaption.container.append(this.close, this.wrapper);
- this.topbar.listenerSetter.add(this.close, 'click', (e) => {
+ this.listenerSetter.add(this.close, 'click', (e) => {
cancelEvent(e);
((onClose ? onClose() : null) || Promise.resolve(true)).then(needClose => {
diff --git a/src/components/chat/pinnedMessage.ts b/src/components/chat/pinnedMessage.ts
index c8b351fc..ca82af09 100644
--- a/src/components/chat/pinnedMessage.ts
+++ b/src/components/chat/pinnedMessage.ts
@@ -1,4 +1,3 @@
-import type { AppImManager } from "../../lib/appManagers/appImManager";
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
import type ChatTopbar from "./topbar";
@@ -8,8 +7,10 @@ import PinnedContainer from "./pinnedContainer";
import PinnedMessageBorder from "./pinnedMessageBorder";
import ReplyContainer, { wrapReplyDivAndCaption } from "./replyContainer";
import rootScope from "../../lib/rootScope";
-import { findUpClassName } from "../../helpers/dom";
+import { cancelEvent, findUpClassName, getElementByPoint, handleScrollSideEvent } from "../../helpers/dom";
import Chat from "./chat";
+import ListenerSetter from "../../helpers/listenerSetter";
+import ButtonIcon from "../buttonIcon";
class AnimatedSuper {
static DURATION = 200;
@@ -191,28 +192,54 @@ class AnimatedCounter {
}
export default class ChatPinnedMessage {
+ public static LOAD_COUNT = 50;
+ public static LOAD_OFFSET = 5;
+
public pinnedMessageContainer: PinnedContainer;
public pinnedMessageBorder: PinnedMessageBorder;
- public pinnedIndex = 0;
+
+ public pinnedMaxMid = 0;
+ public pinnedMid = 0;
+ public pinnedIndex = -1;
public wasPinnedIndex = 0;
+
public locked = false;
public waitForScrollBottom = false;
+ public count = 0;
+ public mids: number[] = [];
+ public offsetIndex = 0;
+
+ public loading = false;
+ public loadedBottom = false;
+ public loadedTop = false;
+
public animatedSubtitle: AnimatedSuper;
public animatedMedia: AnimatedSuper;
public animatedCounter: AnimatedCounter;
+
+ public listenerSetter: ListenerSetter;
+ public scrollDownListenerSetter: ListenerSetter = null;
+
+ public hidden = false;
+
+ public getCurrentIndexPromise: Promise = null;
+ public btnOpen: HTMLButtonElement;
constructor(private topbar: ChatTopbar, private chat: Chat, private appMessagesManager: AppMessagesManager, private appPeersManager: AppPeersManager) {
- this.pinnedMessageContainer = new PinnedContainer(topbar, chat, 'message', new ReplyContainer('pinned-message'), () => {
+ this.listenerSetter = new ListenerSetter();
+
+ this.pinnedMessageContainer = new PinnedContainer(topbar, chat, this.listenerSetter, 'message', new ReplyContainer('pinned-message'), () => {
if(appPeersManager.canPinMessage(this.topbar.peerID)) {
- new PopupPinMessage(this.topbar.peerID, 0);
+ new PopupPinMessage(this.topbar.peerID, this.pinnedMid, true);
return Promise.resolve(false);
+ } else {
+ return this.appMessagesManager.hidePinnedMessages(this.topbar.peerID).then(() => true);
}
});
this.pinnedMessageBorder = new PinnedMessageBorder();
this.pinnedMessageContainer.divAndCaption.border.replaceWith(this.pinnedMessageBorder.render(1, 0));
- this.topbar.btnJoin.parentElement.insertBefore(this.pinnedMessageContainer.divAndCaption.container, this.topbar.btnJoin);
this.animatedSubtitle = new AnimatedSuper();
this.pinnedMessageContainer.divAndCaption.subtitle.append(this.animatedSubtitle.container);
@@ -225,90 +252,261 @@ export default class ChatPinnedMessage {
this.pinnedMessageContainer.divAndCaption.title.innerHTML = 'Pinned Message ';
this.pinnedMessageContainer.divAndCaption.title.append(this.animatedCounter.container);
- this.topbar.listenerSetter.add(rootScope, 'peer_pinned_messages', (e) => {
+ this.btnOpen = ButtonIcon('pinlist pinned-container-close pinned-message-pinlist', {noRipple: true});
+ this.pinnedMessageContainer.divAndCaption.container.prepend(this.btnOpen);
+
+ this.listenerSetter.add(this.btnOpen, 'click', (e) => {
+ cancelEvent(e);
+ this.topbar.openPinned(true);
+ });
+
+ this.listenerSetter.add(rootScope, 'peer_pinned_messages', (e) => {
const peerID = e.detail;
if(peerID == this.topbar.peerID) {
- this.setPinnedMessage();
+ //this.wasPinnedIndex = 0;
+ //setTimeout(() => {
+ if(this.hidden) {
+ this.pinnedMessageContainer.toggle(this.hidden = false);
+ }
+
+ this.loadedTop = this.loadedBottom = false;
+ this.pinnedIndex = -1;
+ this.pinnedMid = 0;
+ this.count = 0;
+ this.mids = [];
+ this.offsetIndex = 0;
+ this.pinnedMaxMid = 0;
+ this.setCorrectIndex(0);
+ //}, 300);
+ }
+ });
+
+ this.listenerSetter.add(rootScope, 'peer_pinned_hidden', (e) => {
+ const {peerID, maxID} = e.detail;
+
+ if(peerID == this.topbar.peerID) {
+ this.pinnedMessageContainer.toggle(this.hidden = true);
}
});
}
- public setCorrectIndex(lastScrollDirection?: number) {
- if(this.locked || this.chat.setPeerPromise) {
- return;
- }/* else if(this.waitForScrollBottom) {
- if(lastScrollDirection === 1) {
- this.waitForScrollBottom = false;
- } else {
- return;
- }
- } */
+ public destroy() {
+ this.pinnedMessageContainer.divAndCaption.container.remove();
+ this.listenerSetter.removeAll();
+ this.unsetScrollDownListener(false);
+ }
- ///const perf = performance.now();
- const rect = this.chat.bubbles.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);
+ public setCorrectIndex(lastScrollDirection?: number) {
+ if(this.locked || this.hidden/* || this.chat.setPeerPromise || this.chat.bubbles.messagesQueuePromise */) {
+ return;
+ }
+
+ if((this.loadedBottom || this.loadedTop) && !this.count) {
+ return;
+ }
+
+ //const perf = performance.now();
+ let el = getElementByPoint(this.chat.bubbles.scrollable.container, 'bottom');
+ //this.chat.log('[PM]: setCorrectIndex: get last element perf:', performance.now() - perf, el);
if(!el) return;
el = findUpClassName(el, 'bubble');
if(!el) return;
- if(el && el.dataset.mid !== undefined) {
- const mid = +el.dataset.mid;
- this.appMessagesManager.getPinnedMessages(this.topbar.peerID).then(mids => {
- let currentIndex = mids.findIndex(_mid => _mid <= mid);
- if(currentIndex === -1) {
- currentIndex = mids.length ? mids.length - 1 : 0;
- }
+ const mid = el.dataset.mid;
+ if(el && mid !== undefined) {
+ this.chat.log('[PM]: setCorrectIndex will test mid:', mid);
+ this.testMid(+mid, lastScrollDirection);
+ }
+ }
- //this.appImManager.log('pinned currentIndex', currentIndex);
+ public testMid(mid: number, lastScrollDirection?: number) {
+ //if(lastScrollDirection !== undefined) return;
+ if(this.hidden) return;
+
+ this.chat.log('[PM]: testMid', mid);
+
+ let currentIndex: number = this.mids.findIndex(_mid => _mid <= mid);
+ if(currentIndex !== -1 && !this.isNeededMore(currentIndex)) {
+ currentIndex += this.offsetIndex;
+ } else if(this.loadedTop && mid < this.mids[this.mids.length - 1]) {
+ //currentIndex = 0;
+ currentIndex = this.mids.length - 1 + this.offsetIndex;
+ } else {
+ if(!this.getCurrentIndexPromise) {
+ this.getCurrentIndexPromise = this.getCurrentIndex(mid, lastScrollDirection !== undefined);
+ }
+
+ return;
+ }
+
+ //const idx = Math.max(0, this.mids.indexOf(mid));
+
+ /* if(currentIndex == this.count) {
+ currentIndex = 0;
+ } */
+
+ this.chat.log('[PM]: testMid: pinned currentIndex', currentIndex, mid);
+
+ const changed = this.pinnedIndex != currentIndex;
+ if(changed) {
+ if(this.waitForScrollBottom && lastScrollDirection !== undefined) {
+ if(this.pinnedIndex === 0 || this.pinnedIndex > currentIndex) { // если не скроллил вниз и пытается поставить нижний пиннед - выйти
+ return;
+ }
+ }
+
+ this.pinnedIndex = currentIndex;
+ this.pinnedMid = this.mids.find(_mid => _mid <= mid) || this.mids[this.mids.length - 1];
+ this.setPinnedMessage();
+ }
+ }
+
+ private isNeededMore(currentIndex: number) {
+ return (this.count > ChatPinnedMessage.LOAD_COUNT &&
+ (
+ (!this.loadedBottom && currentIndex <= ChatPinnedMessage.LOAD_OFFSET) ||
+ (!this.loadedTop && (this.count - 1 - currentIndex) <= ChatPinnedMessage.LOAD_OFFSET)
+ )
+ );
+ }
+
+ private async getCurrentIndex(mid: number, correctAfter = true) {
+ if(this.loading) return;
+ this.loading = true;
+
+ try {
+ let gotRest = false;
+ const promises = [
+ this.appMessagesManager.getSearch(this.topbar.peerID, '', {_: 'inputMessagesFilterPinned'}, mid, ChatPinnedMessage.LOAD_COUNT, 0, ChatPinnedMessage.LOAD_COUNT)
+ .then(r => {
+ gotRest = true;
+ return r;
+ })
+ ];
- const changed = this.pinnedIndex != currentIndex;
- if(changed) {
- if(this.waitForScrollBottom) {
- if(lastScrollDirection === 1) { // если проскроллил вниз - разблокировать
- this.waitForScrollBottom = false;
- } else if(this.pinnedIndex > currentIndex) { // если не скроллил вниз и пытается поставить нижний пиннед - выйти
- return;
- }
- }
+ if(!this.pinnedMaxMid) {
+ const promise = this.appMessagesManager.getPinnedMessage(this.topbar.peerID).then(p => {
+ if(!p.maxID) return;
+ this.pinnedMaxMid = p.maxID;
- this.pinnedIndex = currentIndex;
- this.setPinnedMessage();
- }
- });
+ if(!gotRest && correctAfter) {
+ this.mids = [this.pinnedMaxMid];
+ this.count = p.count;
+ this.pinnedIndex = 0;
+ this.pinnedMid = this.mids[0];
+ this.setPinnedMessage();
+ //this.pinnedMessageContainer.toggle(false);
+ }
+ });
+
+ promises.push(promise as any);
+ }
+
+ const result = (await Promise.all(promises))[0];
+
+ let backLimited = result.history.findIndex(_mid => _mid <= mid);
+ if(backLimited === -1) {
+ backLimited = result.history.length;
+ }/* else {
+ backLimited -= 1;
+ } */
+
+ this.offsetIndex = result.offset_id_offset ? result.offset_id_offset - backLimited : 0;
+ this.mids = result.history.slice();
+ this.count = result.count;
+
+ if(!this.count) {
+ this.pinnedMessageContainer.toggle(true);
+ }
+
+ this.loadedTop = (this.offsetIndex + this.mids.length) == this.count;
+ this.loadedBottom = !this.offsetIndex;
+
+ this.chat.log('[PM]: getCurrentIndex result:', mid, result, backLimited, this.offsetIndex, this.loadedTop, this.loadedBottom);
+ } catch(err) {
+ this.chat.log.error('[PM]: getCurrentIndex error', err);
+ }
+
+ this.loading = false;
+
+ if(this.locked) {
+ this.testMid(mid);
+ } else if(correctAfter) {
+ this.setCorrectIndex(0);
+ }
+
+ this.getCurrentIndexPromise = null;
+ //return result.offset_id_offset || 0;
+ }
+
+ public setScrollDownListener() {
+ this.waitForScrollBottom = true;
+
+ if(!this.scrollDownListenerSetter) {
+ this.scrollDownListenerSetter = new ListenerSetter();
+ handleScrollSideEvent(this.chat.bubbles.scrollable.container, 'bottom', () => {
+ this.unsetScrollDownListener();
+ }, this.scrollDownListenerSetter);
+ }
+ }
+
+ public unsetScrollDownListener(refreshPosition = true) {
+ this.waitForScrollBottom = false;
+
+ if(this.scrollDownListenerSetter) {
+ this.scrollDownListenerSetter.removeAll();
+ this.scrollDownListenerSetter = null;
+ }
+
+ if(refreshPosition) {
+ this.setCorrectIndex(0);
+ }
+ }
+
+ public async handleFollowingPinnedMessage() {
+ this.locked = true;
+
+ this.chat.log('[PM]: handleFollowingPinnedMessage');
+ try {
+ this.setScrollDownListener();
+
+ const setPeerPromise = this.chat.setPeerPromise;
+ if(setPeerPromise instanceof Promise) {
+ await setPeerPromise;
+ }
+
+ await this.chat.bubbles.scrollable.scrollLockedPromise;
+
+ if(this.getCurrentIndexPromise) {
+ await this.getCurrentIndexPromise;
+ }
+
+ this.chat.log('[PM]: handleFollowingPinnedMessage: unlock');
+ this.locked = false;
+
+ /* // подождём, пока скролл остановится
+ setTimeout(() => {
+ this.chat.log('[PM]: handleFollowingPinnedMessage: unlock');
+ this.locked = false;
+ }, 50); */
+ } catch(err) {
+ this.chat.log.error('[PM]: handleFollowingPinnedMessage error:', err);
+
+ this.locked = false;
+ this.waitForScrollBottom = false;
+ this.setCorrectIndex(0);
}
}
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.chat.setPeer(message.peerID, mid);
- if(setPeerPromise instanceof Promise) {
- await setPeerPromise;
- }
-
- await this.chat.bubbles.scrollable.scrollLockedPromise;
- } catch(err) {
- this.chat.log.error('[PM]: followPinnedMessage error:', err);
- }
-
- // подождём, пока скролл остановится
- setTimeout(() => {
- this.locked = false;
- this.waitForScrollBottom = true;
- }, 50);
+ this.chat.setPeer(this.topbar.peerID, mid);
+ (this.chat.setPeerPromise || Promise.resolve()).then(() => { // * debounce fast clicker
+ this.handleFollowingPinnedMessage();
+ this.testMid(this.pinnedIndex >= (this.count - 1) ? this.pinnedMaxMid : mid - 1);
+ });
}
}
@@ -320,24 +518,24 @@ export default class ChatPinnedMessage {
public setPinnedMessage() {
/////this.log('setting pinned message', message);
//return;
- const promise: Promise = this.chat.setPeerPromise || this.chat.bubbles.messagesQueuePromise || Promise.resolve();
+ /* const promise: Promise = this.chat.setPeerPromise || this.chat.bubbles.messagesQueuePromise || Promise.resolve();
Promise.all([
- this.appMessagesManager.getPinnedMessages(this.topbar.peerID),
promise
- ]).then(([mids]) => {
+ ]).then(() => { */
//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]);
+ const count = this.count;
+ if(count) {
+ const pinnedIndex = this.pinnedIndex;
+ const message = this.appMessagesManager.getMessage(this.pinnedMid);
- //this.animatedCounter.prepareNumber(mids.length);
+ //this.animatedCounter.prepareNumber(count);
//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);
+ this.animatedCounter.setCount(count - pinnedIndex);
}
//}, 100);
@@ -372,13 +570,15 @@ export default class ChatPinnedMessage {
}
//}
- this.pinnedMessageBorder.render(mids.length, mids.length - pinnedIndex - 1);
+ this.pinnedMessageBorder.render(count, count - pinnedIndex - 1);
this.wasPinnedIndex = pinnedIndex;
this.pinnedMessageContainer.divAndCaption.container.dataset.mid = '' + message.mid;
} else {
this.pinnedMessageContainer.toggle(true);
this.wasPinnedIndex = 0;
}
- });
+
+ this.pinnedMessageContainer.divAndCaption.container.classList.toggle('is-many', this.count > 1);
+ //});
}
}
\ No newline at end of file
diff --git a/src/components/chat/selection.ts b/src/components/chat/selection.ts
index 20dad727..8a45b58f 100644
--- a/src/components/chat/selection.ts
+++ b/src/components/chat/selection.ts
@@ -28,9 +28,9 @@ export default class ChatSelection {
private listenerSetter: ListenerSetter;
- constructor(private chatBubbles: ChatBubbles, private chatInput: ChatInput, private appMessagesManager: AppMessagesManager) {
- const bubblesContainer = chatBubbles.bubblesContainer;
- this.listenerSetter = chatBubbles.listenerSetter;
+ constructor(private bubbles: ChatBubbles, private input: ChatInput, private appMessagesManager: AppMessagesManager) {
+ const bubblesContainer = bubbles.bubblesContainer;
+ this.listenerSetter = bubbles.listenerSetter;
if(isTouchSupported) {
this.listenerSetter.add(bubblesContainer, 'touchend', (e) => {
@@ -49,6 +49,7 @@ export default class ChatSelection {
|| (
!this.selectedMids.size
&& !(e.target as HTMLElement).classList.contains('bubble')
+ && !(e.target as HTMLElement).classList.contains('document-selection')
&& bubble
)
) {
@@ -86,7 +87,7 @@ export default class ChatSelection {
/* if(foundTargets.has(e.target as HTMLElement)) return;
foundTargets.set(e.target as HTMLElement, true); */
- const bubble = findUpClassName(e.target, 'bubble');
+ const bubble = findUpClassName(e.target, 'grouped-item') || findUpClassName(e.target, 'bubble');
if(!bubble) {
//console.error('found no bubble', e);
return;
@@ -96,7 +97,7 @@ export default class ChatSelection {
if(!mid) return;
// * cancel selecting if selecting message text
- if(e.target != bubble && selecting === undefined && !this.selectedMids.size) {
+ if(e.target != bubble && !(e.target as HTMLElement).classList.contains('document-selection') && selecting === undefined && !this.selectedMids.size) {
this.listenerSetter.removeManual(bubblesContainer, 'mousemove', onMouseMove);
this.listenerSetter.removeManual(document, 'mouseup', onMouseUp, documentListenerOptions);
return;
@@ -115,7 +116,7 @@ export default class ChatSelection {
if(!this.selectedMids.size) {
if(seen.size == 2) {
[...seen].forEach(mid => {
- const mounted = this.chatBubbles.getMountedBubble(mid);
+ const mounted = this.bubbles.getMountedBubble(mid);
if(mounted) {
this.toggleByBubble(mounted.bubble);
}
@@ -151,7 +152,7 @@ export default class ChatSelection {
public toggleBubbleCheckbox(bubble: HTMLElement, show: boolean) {
const hasCheckbox = !!this.getCheckboxInputFromBubble(bubble);
- const isAlbum = bubble.classList.contains('is-album');
+ const isGrouped = bubble.classList.contains('is-grouped');
if(show) {
if(hasCheckbox) return;
@@ -160,23 +161,44 @@ export default class ChatSelection {
// * if it is a render of new message
const mid = +bubble.dataset.mid;
- if(this.selectedMids.has(mid) && (!isAlbum || this.isAlbumMidsSelected(mid))) {
+ if(this.selectedMids.has(mid) && (!isGrouped || this.isGroupedMidsSelected(mid))) {
checkboxField.input.checked = true;
bubble.classList.add('is-selected');
}
- bubble.prepend(checkboxField.label);
+ if(bubble.classList.contains('document-container')) {
+ bubble.querySelector('.document, audio-element').append(checkboxField.label);
+ } else {
+ bubble.prepend(checkboxField.label);
+ }
} else if(hasCheckbox) {
- bubble.firstElementChild.remove();
+ this.getCheckboxInputFromBubble(bubble).parentElement.remove();
}
- if(isAlbum) {
- this.chatBubbles.getBubbleAlbumItems(bubble).forEach(item => this.toggleBubbleCheckbox(item, show));
+ if(isGrouped) {
+ this.bubbles.getBubbleGroupedItems(bubble).forEach(item => this.toggleBubbleCheckbox(item, show));
}
}
- public getCheckboxInputFromBubble(bubble: HTMLElement) {
- return bubble.firstElementChild.tagName == 'LABEL' && bubble.firstElementChild.firstElementChild as HTMLInputElement;
+ public getCheckboxInputFromBubble(bubble: HTMLElement): HTMLInputElement {
+ /* let perf = performance.now();
+ let checkbox = bubble.firstElementChild.tagName == 'LABEL' && bubble.firstElementChild.firstElementChild as HTMLInputElement;
+ console.log('getCheckboxInputFromBubble firstElementChild time:', performance.now() - perf);
+
+ perf = performance.now();
+ checkbox = bubble.querySelector('label input');
+ console.log('getCheckboxInputFromBubble querySelector time:', performance.now() - perf); */
+ /* let perf = performance.now();
+ let contains = bubble.classList.contains('document-container');
+ console.log('getCheckboxInputFromBubble classList time:', performance.now() - perf);
+
+ perf = performance.now();
+ contains = bubble.className.includes('document-container');
+ console.log('getCheckboxInputFromBubble className time:', performance.now() - perf); */
+
+ return bubble.classList.contains('document-container') ?
+ bubble.querySelector('label input') :
+ bubble.firstElementChild.tagName == 'LABEL' && bubble.firstElementChild.firstElementChild as HTMLInputElement;
}
public updateForwardContainer(forceSelection = false) {
@@ -213,7 +235,7 @@ export default class ChatSelection {
if(wasSelecting == this.isSelecting) return;
- const bubblesContainer = this.chatBubbles.bubblesContainer;
+ const bubblesContainer = this.bubbles.bubblesContainer;
//bubblesContainer.classList.toggle('is-selecting', !!this.selectedMids.size);
/* if(bubblesContainer.classList.contains('is-chat-input-hidden')) {
@@ -234,7 +256,9 @@ export default class ChatSelection {
blurActiveElement(); // * for mobile keyboards
- SetTransition(bubblesContainer, 'is-selecting', !!this.selectedMids.size || forceSelection, 200, () => {
+ const forwards = !!this.selectedMids.size || forceSelection;
+ SetTransition(this.input.rowsWrapper, 'is-centering', forwards, 200);
+ SetTransition(bubblesContainer, 'is-selecting', forwards, 200, () => {
if(!this.isSelecting) {
this.selectionContainer.remove();
this.selectionContainer = this.selectionForwardBtn = this.selectionDeleteBtn = null;
@@ -242,7 +266,7 @@ export default class ChatSelection {
}
window.requestAnimationFrame(() => {
- this.chatBubbles.onScroll();
+ this.bubbles.onScroll();
});
});
@@ -277,13 +301,13 @@ export default class ChatSelection {
this.selectionContainer.append(btnCancel, this.selectionCountEl, this.selectionForwardBtn, this.selectionDeleteBtn);
- this.chatInput.rowsWrapper.append(this.selectionContainer);
+ this.input.rowsWrapper.append(this.selectionContainer);
}
}
if(toggleCheckboxes) {
- for(const mid in this.chatBubbles.bubbles) {
- const bubble = this.chatBubbles.bubbles[mid];
+ for(const mid in this.bubbles.bubbles) {
+ const bubble = this.bubbles.bubbles[mid];
this.toggleBubbleCheckbox(bubble, this.isSelecting);
}
}
@@ -295,9 +319,10 @@ export default class ChatSelection {
public cancelSelection = () => {
for(const mid of this.selectedMids) {
- const mounted = this.chatBubbles.getMountedBubble(mid);
+ const mounted = this.bubbles.getMountedBubble(mid);
if(mounted) {
- this.toggleByBubble(mounted.message.grouped_id ? mounted.bubble.querySelector(`.album-item[data-mid="${mid}"]`) : mounted.bubble);
+ //this.toggleByBubble(mounted.message.grouped_id ? mounted.bubble.querySelector(`.grouped-item[data-mid="${mid}"]`) : mounted.bubble);
+ this.toggleByBubble(mounted.bubble);
}
/* const bubble = this.appImManager.bubbles[mid];
if(bubble) {
@@ -325,12 +350,12 @@ export default class ChatSelection {
SetTransition(bubble, 'is-selected', isSelected, 200);
}
- public isAlbumBubbleSelected(bubble: HTMLElement) {
- const albumCheckboxInput = this.getCheckboxInputFromBubble(bubble);
- return albumCheckboxInput?.checked;
+ public isGroupedBubbleSelected(bubble: HTMLElement) {
+ const groupedCheckboxInput = this.getCheckboxInputFromBubble(bubble);
+ return groupedCheckboxInput?.checked;
}
- public isAlbumMidsSelected(mid: number) {
+ public isGroupedMidsSelected(mid: number) {
const mids = this.appMessagesManager.getMidsByMid(mid);
const selectedMids = mids.filter(mid => this.selectedMids.has(mid));
return mids.length == selectedMids.length;
@@ -339,14 +364,14 @@ export default class ChatSelection {
public toggleByBubble = (bubble: HTMLElement) => {
const mid = +bubble.dataset.mid;
- const isAlbum = bubble.classList.contains('is-album');
- if(isAlbum) {
- if(!this.isAlbumBubbleSelected(bubble)) {
+ const isGrouped = bubble.classList.contains('is-grouped');
+ if(isGrouped) {
+ if(!this.isGroupedBubbleSelected(bubble)) {
const mids = this.appMessagesManager.getMidsByMid(mid);
mids.forEach(mid => this.selectedMids.delete(mid));
}
- this.chatBubbles.getBubbleAlbumItems(bubble).forEach(this.toggleByBubble);
+ this.bubbles.getBubbleGroupedItems(bubble).forEach(this.toggleByBubble);
return;
}
@@ -376,15 +401,15 @@ export default class ChatSelection {
this.selectedMids.add(mid);
}
- const isAlbumItem = bubble.classList.contains('album-item');
- if(isAlbumItem) {
- const albumContainer = findUpClassName(bubble, 'bubble');
- const isAlbumSelected = this.isAlbumBubbleSelected(albumContainer);
- const isAlbumMidsSelected = this.isAlbumMidsSelected(mid);
+ const isGroupedItem = bubble.classList.contains('grouped-item');
+ if(isGroupedItem) {
+ const groupContainer = findUpClassName(bubble, 'bubble');
+ const isGroupedSelected = this.isGroupedBubbleSelected(groupContainer);
+ const isGroupedMidsSelected = this.isGroupedMidsSelected(mid);
- const willChange = isAlbumMidsSelected || isAlbumSelected;
+ const willChange = isGroupedMidsSelected || isGroupedSelected;
if(willChange) {
- this.updateBubbleSelection(albumContainer, isAlbumMidsSelected);
+ this.updateBubbleSelection(groupContainer, isGroupedMidsSelected);
}
}
diff --git a/src/components/chat/topbar.ts b/src/components/chat/topbar.ts
index d0030979..3fbf866c 100644
--- a/src/components/chat/topbar.ts
+++ b/src/components/chat/topbar.ts
@@ -1,8 +1,6 @@
import type { AppChatsManager, Channel } from "../../lib/appManagers/appChatsManager";
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
-import type { AppProfileManager } from "../../lib/appManagers/appProfileManager";
-import type { AppUsersManager } from "../../lib/appManagers/appUsersManager";
import type { AppSidebarRight } from "../sidebarRight";
import type Chat from "./chat";
import { findUpClassName, cancelEvent, attachClickEvent } from "../../helpers/dom";
@@ -18,6 +16,7 @@ import ChatPinnedMessage from "./pinnedMessage";
import ChatSearch from "./search";
import { ButtonMenuItemOptions } from "../buttonMenu";
import ListenerSetter from "../../helpers/listenerSetter";
+import appStateManager from "../../lib/appManagers/appStateManager";
export default class ChatTopbar {
container: HTMLDivElement;
@@ -38,15 +37,20 @@ export default class ChatTopbar {
private setUtilsRAF: number;
public peerID: number;
+ public wasPeerID: number;
private setPeerStatusInterval: number;
public listenerSetter: ListenerSetter;
- constructor(private chat: Chat, private appSidebarRight: AppSidebarRight, private appMessagesManager: AppMessagesManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager, private appUsersManager: AppUsersManager, private appProfileManager: AppProfileManager) {
+ public menuButtons: (ButtonMenuItemOptions & {verify: () => boolean})[] = [];
+
+ constructor(private chat: Chat, private appSidebarRight: AppSidebarRight, private appMessagesManager: AppMessagesManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager) {
+ this.listenerSetter = new ListenerSetter();
+ }
+
+ public construct() {
this.chat.log.error('Topbar construction');
- this.listenerSetter = new ListenerSetter();
-
this.container = document.createElement('div');
this.container.classList.add('sidebar-header', 'topbar');
@@ -59,10 +63,6 @@ export default class ChatTopbar {
const person = document.createElement('div');
person.classList.add('person');
- this.avatarElement = new AvatarElement();
- this.avatarElement.setAttribute('dialog', '1');
- this.avatarElement.setAttribute('clickable', '');
-
const content = document.createElement('div');
content.classList.add('content');
@@ -77,13 +77,16 @@ export default class ChatTopbar {
const bottom = document.createElement('div');
bottom.classList.add('bottom');
- this.subtitle = document.createElement('div');
- this.subtitle.classList.add('info');
-
- bottom.append(this.subtitle);
+ if(this.subtitle) {
+ bottom.append(this.subtitle);
+ }
content.append(top, bottom);
- person.append(this.avatarElement, content);
+ if(this.avatarElement) {
+ person.append(this.avatarElement);
+ }
+
+ person.append(content);
this.chatInfo.append(person);
// * chat utils section
@@ -92,25 +95,77 @@ export default class ChatTopbar {
this.chatAudio = new ChatAudio(this, this.chat, this.appMessagesManager, this.appPeersManager);
+ if(this.menuButtons.length) {
+ this.btnMore = ButtonMenuToggle({listenerSetter: this.listenerSetter}, 'bottom-left', this.menuButtons, () => {
+ this.menuButtons.forEach(button => {
+ button.element.classList.toggle('hide', !button.verify());
+ });
+ });
+ }
+
+ this.chatUtils.append(...[this.chatAudio ? this.chatAudio.divAndCaption.container : null, this.pinnedMessage ? this.pinnedMessage.pinnedMessageContainer.divAndCaption.container : null, this.btnJoin, this.btnPinned, this.btnMute, this.btnSearch, this.btnMore].filter(Boolean));
+
+ this.container.append(this.btnBack, this.chatInfo, this.chatUtils);
+
+ // * construction end
+
+ // * fix topbar overflow section
+
+ this.listenerSetter.add(window, 'resize', this.onResize);
+ mediaSizes.addListener('changeScreen', this.onChangeScreen);
+
+ this.listenerSetter.add(this.container, 'click', (e) => {
+ const pinned: HTMLElement = findUpClassName(e.target, 'pinned-container');
+ if(pinned) {
+ cancelEvent(e);
+
+ const mid = +pinned.dataset.mid;
+ if(pinned.classList.contains('pinned-message')) {
+ //if(!this.pinnedMessage.locked) {
+ this.pinnedMessage.followPinnedMessage(mid);
+ //}
+ } else {
+ const message = this.appMessagesManager.getMessage(mid);
+
+ this.chat.setPeer(message.peerID, mid);
+ }
+ } else {
+ this.appSidebarRight.toggleSidebar(true);
+ }
+ });
+
+ this.listenerSetter.add(this.btnBack, 'click', (e) => {
+ cancelEvent(e);
+ this.chat.appImManager.setPeer(0);
+ });
+ }
+
+ public constructPeerHelpers() {
+ this.avatarElement = new AvatarElement();
+ this.avatarElement.setAttribute('dialog', '1');
+ this.avatarElement.setAttribute('clickable', '');
+
+ this.subtitle = document.createElement('div');
+ this.subtitle.classList.add('info');
+
+ this.pinnedMessage = new ChatPinnedMessage(this, this.chat, this.appMessagesManager, this.appPeersManager);
+
this.btnJoin = Button('btn-primary chat-join hide');
this.btnJoin.append('SUBSCRIBE');
- this.btnPinned = ButtonIcon('pinlist');
- this.btnMute = ButtonIcon('mute');
- this.btnSearch = ButtonIcon('search');
- const menuButtons: (ButtonMenuItemOptions & {verify: () => boolean})[] = [{
+ this.menuButtons = [{
icon: 'search',
text: 'Search',
onClick: () => {
new ChatSearch(this, this.chat);
},
verify: () => mediaSizes.isMobile
- }, {
+ }, /* {
icon: 'pinlist',
text: 'Pinned Messages',
- onClick: () => {},
+ onClick: () => this.openPinned(false),
verify: () => mediaSizes.isMobile
- }, {
+ }, */ {
icon: 'mute',
text: 'Mute',
onClick: () => {
@@ -144,53 +199,20 @@ export default class ChatTopbar {
onClick: () => {},
verify: () => true
}];
- //menuButtons.forEach(b => b.options = {listenerSetter: this.listenerSetter});
- this.btnMore = ButtonMenuToggle({listenerSetter: this.listenerSetter}, 'bottom-left', menuButtons, () => {
- menuButtons.forEach(button => {
- button.element.classList.toggle('hide', !button.verify());
- });
- });
- this.chatUtils.append(this.chatAudio.divAndCaption.container, this.btnJoin, this.btnPinned, this.btnMute, this.btnSearch, this.btnMore);
+ this.btnPinned = ButtonIcon('pinlist');
+ this.btnMute = ButtonIcon('mute');
+ this.btnSearch = ButtonIcon('search');
- this.container.append(this.btnBack, this.chatInfo, this.chatUtils);
-
- // * construction end
-
- // * fix topbar overflow section
-
- this.listenerSetter.add(window, 'resize', this.onResize);
- mediaSizes.addListener('changeScreen', this.onChangeScreen);
-
- this.pinnedMessage = new ChatPinnedMessage(this, this.chat, this.appMessagesManager, this.appPeersManager);
-
- this.listenerSetter.add(this.container, 'click', (e) => {
- const pinned: HTMLElement = findUpClassName(e.target, 'pinned-container');
- if(pinned) {
- cancelEvent(e);
-
- const mid = +pinned.dataset.mid;
- if(pinned.classList.contains('pinned-message')) {
- this.pinnedMessage.followPinnedMessage(mid);
- } else {
- const message = this.appMessagesManager.getMessage(mid);
-
- this.chat.setPeer(message.peerID, mid);
- }
- } else {
- this.appSidebarRight.toggleSidebar(true);
- }
- });
-
- this.listenerSetter.add(this.btnBack, 'click', (e) => {
+ this.listenerSetter.add(this.btnPinned, 'click', (e) => {
cancelEvent(e);
- this.chat.appImManager.setPeer(0);
+ this.openPinned(true);
});
this.listenerSetter.add(this.btnSearch, 'click', (e) => {
cancelEvent(e);
if(this.peerID) {
- appSidebarRight.searchTab.open(this.peerID);
+ this.appSidebarRight.searchTab.open(this.peerID);
}
});
@@ -199,12 +221,11 @@ export default class ChatTopbar {
this.appMessagesManager.mutePeer(this.peerID);
});
- //this.listenerSetter.add(this.btnJoin, 'mousedown', (e) => {
attachClickEvent(this.btnJoin, (e) => {
cancelEvent(e);
this.btnJoin.setAttribute('disabled', 'true');
- appChatsManager.joinChannel(-this.peerID).finally(() => {
+ this.appChatsManager.joinChannel(-this.peerID).finally(() => {
this.btnJoin.removeAttribute('disabled');
});
//});
@@ -213,7 +234,7 @@ export default class ChatTopbar {
this.listenerSetter.add(rootScope, 'chat_update', (e) => {
const peerID: number = e.detail;
if(this.peerID == -peerID) {
- const chat = appChatsManager.getChat(peerID) as Channel/* | Chat */;
+ const chat = this.appChatsManager.getChat(peerID) as Channel/* | Chat */;
this.btnJoin.classList.toggle('hide', !(chat as Channel)?.pFlags?.left);
this.setUtilsWidth();
@@ -244,7 +265,23 @@ export default class ChatTopbar {
}
});
+ this.chat.addListener('setPeer', (mid, isTopMessage) => {
+ if(isTopMessage) {
+ this.pinnedMessage.unsetScrollDownListener();
+ this.pinnedMessage.testMid(mid, 0); // * because slider will not let get bubble by document.elementFromPoint
+ } else if(!this.pinnedMessage.locked) {
+ this.pinnedMessage.handleFollowingPinnedMessage();
+ this.pinnedMessage.testMid(mid);
+ }
+ });
+
this.setPeerStatusInterval = window.setInterval(this.setPeerStatus, 60e3);
+
+ return this;
+ }
+
+ public openPinned(byCurrent: boolean) {
+ this.chat.appImManager.setInnerPeer(this.peerID, byCurrent ? +this.pinnedMessage.pinnedMessageContainer.divAndCaption.container.dataset.mid : 0, 'pinned');
}
private onResize = () => {
@@ -252,8 +289,8 @@ export default class ChatTopbar {
};
private onChangeScreen = (from: ScreenSize, to: ScreenSize) => {
- this.chatAudio.divAndCaption.container.classList.toggle('is-floating', to == ScreenSize.mobile);
- this.pinnedMessage.onChangeScreen(from, to);
+ this.chatAudio && this.chatAudio.divAndCaption.container.classList.toggle('is-floating', to == ScreenSize.mobile);
+ this.pinnedMessage && this.pinnedMessage.onChangeScreen(from, to);
this.setUtilsWidth(true);
};
@@ -263,48 +300,86 @@ export default class ChatTopbar {
this.listenerSetter.removeAll();
mediaSizes.removeListener('changeScreen', this.onChangeScreen);
window.clearInterval(this.setPeerStatusInterval);
+
+ if(this.pinnedMessage) {
+ this.pinnedMessage.destroy(); // * возможно это можно не делать
+ }
delete this.chatAudio;
delete this.pinnedMessage;
}
public setPeer(peerID: number) {
+ this.wasPeerID = this.peerID;
this.peerID = peerID;
- this.avatarElement.setAttribute('peer', '' + peerID);
- this.avatarElement.update();
+ this.container.style.display = peerID ? '' : 'none';
+ }
+
+ public finishPeerChange(isTarget: boolean, isJump: boolean, lastMsgID: number) {
+ const peerID = this.peerID;
+
+ if(this.avatarElement) {
+ this.avatarElement.setAttribute('peer', '' + peerID);
+ this.avatarElement.update();
+ }
this.container.classList.remove('is-pinned-shown');
- this.container.style.display = peerID ? '' : 'none';
const isBroadcast = this.appPeersManager.isBroadcast(peerID);
- this.btnMute.classList.toggle('hide', !isBroadcast);
- this.btnJoin.classList.toggle('hide', !this.appChatsManager.getChat(-peerID)?.pFlags?.left);
+ this.btnMute && this.btnMute.classList.toggle('hide', !isBroadcast);
+ this.btnJoin && this.btnJoin.classList.toggle('hide', !this.appChatsManager.getChat(-peerID)?.pFlags?.left);
this.setUtilsWidth();
+ const middleware = this.chat.bubbles.getMiddleware();
+ if(this.pinnedMessage) { // * replace with new one
+ if(this.wasPeerID) { // * change
+ const newPinnedMessage = new ChatPinnedMessage(this, this.chat, this.appMessagesManager, this.appPeersManager);
+ this.pinnedMessage.pinnedMessageContainer.divAndCaption.container.replaceWith(newPinnedMessage.pinnedMessageContainer.divAndCaption.container);
+ this.pinnedMessage.destroy();
+ this.pinnedMessage = newPinnedMessage;
+ }
+
+ appStateManager.getState().then((state) => {
+ if(!middleware()) return;
+
+ this.pinnedMessage.hidden = !!state.hiddenPinnedMessages[peerID];
+
+ if(!isTarget) {
+ this.pinnedMessage.setCorrectIndex(0);
+ }
+ });
+ }
+
window.requestAnimationFrame(() => {
- this.pinnedMessage.pinnedIndex/* = this.pinnedMessage.wasPinnedIndex */ = 0;
- //this.pinnedMessage.setCorrectIndex();
- this.pinnedMessage.setPinnedMessage();
- /* noTransition.forEach(el => {
- el.classList.remove('no-transition-all');
- }); */
- /* if(needToChangeInputDisplay) {
- this.chatInput.style.display = '';
- } */
-
- let title = '';
- if(peerID == rootScope.myID) title = 'Saved Messages';
- else title = this.appPeersManager.getPeerTitle(peerID);
- this.title.innerHTML = title;
-
+ this.setTitle();
this.setPeerStatus(true);
this.setMutedState();
});
}
+ public setTitle(count?: number) {
+ let title = '';
+ if(this.chat.type == 'pinned') {
+ title = count === -1 ? 'Pinned Messages' : (count === 1 ? 'Pinned Message' : (count + ' Pinned Messages'));
+
+ if(count === undefined) {
+ this.appMessagesManager.getSearchCounters(this.peerID, [{_: 'inputMessagesFilterPinned'}]).then(result => {
+ this.setTitle(result[0].count);
+ });
+ }
+ } else {
+ if(this.peerID == rootScope.myID) title = 'Saved Messages';
+ else title = this.appPeersManager.getPeerTitle(this.peerID);
+ }
+
+ this.title.innerHTML = title;
+ }
+
public setMutedState() {
+ if(!this.btnMute) return;
+
const peerID = this.peerID;
let muted = this.appMessagesManager.isPeerMuted(peerID);
if(this.appPeersManager.isBroadcast(peerID)) { // not human
@@ -350,6 +425,8 @@ export default class ChatTopbar {
};
public setPeerStatus = (needClear = false) => {
+ if(!this.subtitle) return;
+
const peerID = this.peerID;
if(needClear) {
this.subtitle.innerHTML = '';
diff --git a/src/components/checkbox.ts b/src/components/checkbox.ts
index bc97f79a..a26489b6 100644
--- a/src/components/checkbox.ts
+++ b/src/components/checkbox.ts
@@ -8,6 +8,7 @@ const CheckboxField = (text: string, name: string, round = false) => {
const span = document.createElement('span');
span.classList.add('checkbox-caption');
+ if(round) span.classList.add('tgico-check');
if(text) {
span.innerText = text;
}
diff --git a/src/components/popupUnpinMessage.ts b/src/components/popupUnpinMessage.ts
index 7454ae6a..f80f93cc 100644
--- a/src/components/popupUnpinMessage.ts
+++ b/src/components/popupUnpinMessage.ts
@@ -6,7 +6,11 @@ export default class PopupPinMessage {
constructor(peerID: number, mid: number, unpin?: true) {
let title: string, description: string, buttons: PopupButton[] = [];
- const callback = () => appMessagesManager.updatePinnedMessage(peerID, mid, unpin);
+ const callback = () => {
+ setTimeout(() => { // * костыль, потому что document.elementFromPoint вернёт popup-peer пока он будет закрываться
+ appMessagesManager.updatePinnedMessage(peerID, mid, unpin);
+ }, 300);
+ };
if(unpin) {
title = `Unpin Message?`;
description = 'Would you like to unpin this message?';
diff --git a/src/components/scrollable.ts b/src/components/scrollable.ts
index 02ad26dc..7f179e65 100644
--- a/src/components/scrollable.ts
+++ b/src/components/scrollable.ts
@@ -70,7 +70,7 @@ export class ScrollableBase {
}
protected setListeners() {
- window.addEventListener('resize', this.onScroll);
+ window.addEventListener('resize', this.onScroll, {passive: true});
this.container.addEventListener('scroll', this.onScroll, {passive: true, capture: true});
}
@@ -160,7 +160,7 @@ export default class Scrollable extends ScrollableBase {
this.lastScrollDirection = this.lastScrollTop == scrollTop ? 0 : (this.lastScrollTop < scrollTop ? 1 : -1); // * 1 - bottom, -1 - top
this.lastScrollTop = scrollTop;
- if(this.onAdditionalScroll) {
+ if(this.onAdditionalScroll && this.lastScrollDirection !== 0) {
this.onAdditionalScroll();
}
diff --git a/src/components/sidebarLeft/tabs/includedChats.ts b/src/components/sidebarLeft/tabs/includedChats.ts
index 78fb3e4b..f948e316 100644
--- a/src/components/sidebarLeft/tabs/includedChats.ts
+++ b/src/components/sidebarLeft/tabs/includedChats.ts
@@ -91,7 +91,7 @@ export default class AppIncludedChatsTab implements SliderTab {
}
checkbox(selected?: boolean) {
- return ``;
+ return ``;
}
renderResults = async(peerIDs: number[]) => {
diff --git a/src/components/sidebarRight/tabs/sharedMedia.ts b/src/components/sidebarRight/tabs/sharedMedia.ts
index 7c5474a5..735126f5 100644
--- a/src/components/sidebarRight/tabs/sharedMedia.ts
+++ b/src/components/sidebarRight/tabs/sharedMedia.ts
@@ -15,7 +15,7 @@ import LazyLoadQueue from "../../lazyLoadQueue";
import { putPreloader, renderImageFromUrl } from "../../misc";
import Scrollable from "../../scrollable";
import { SliderTab } from "../../slider";
-import { wrapAudio, wrapDocument } from "../../wrappers";
+import { wrapDocument } from "../../wrappers";
const testScroll = false;
@@ -101,6 +101,7 @@ export default class AppSharedMediaTab implements SliderTab {
private log = logger('SM'/* , LogLevels.error */);
setPeerStatusInterval: number;
+ cleaned: boolean;
public init() {
this.container = document.getElementById('shared-media-container');
@@ -804,6 +805,7 @@ export default class AppSharedMediaTab implements SliderTab {
});
this.sharedMediaType = 'inputMessagesFilterPhotoVideo';
+ this.cleaned = true;
}
public cleanupHTML() {
@@ -861,6 +863,8 @@ export default class AppSharedMediaTab implements SliderTab {
}
public setPeer(peerID: number) {
+ if(this.peerID == peerID) return;
+
if(this.init) {
this.init();
this.init = null;
@@ -871,7 +875,10 @@ export default class AppSharedMediaTab implements SliderTab {
}
public fillProfileElements() {
- let peerID = this.peerID = appImManager.chat.peerID;
+ if(!this.cleaned) return;
+ this.cleaned = false;
+
+ const peerID = this.peerID;
this.cleanupHTML();
@@ -906,7 +913,7 @@ export default class AppSharedMediaTab implements SliderTab {
setText(user.rPhone, this.profileElements.phone);
}
- appProfileManager.getProfile(peerID, true).then(userFull => {
+ appProfileManager.getProfile(peerID).then(userFull => {
if(this.peerID != peerID) {
this.log.warn('peer changed');
return;
diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts
index bca6596c..6ab15dd4 100644
--- a/src/components/wrappers.ts
+++ b/src/components/wrappers.ts
@@ -24,6 +24,7 @@ import { renderImageFromUrl } from './misc';
import PollElement from './poll';
import ProgressivePreloader from './preloader';
import './middleEllipsis';
+import { nextRandomInt } from '../helpers/random';
const MAX_VIDEO_AUTOPLAY_SIZE = 50 * 1024 * 1024; // 50 MB
@@ -432,7 +433,7 @@ function wrapMediaWithTail(photo: MyPhoto | MyDocument, message: {mid: number, m
svg.setAttributeNS(null, 'viewBox', '0 0 ' + width + ' ' + height);
svg.setAttributeNS(null, 'preserveAspectRatio', 'none');
- const clipID = 'clip' + message.mid;
+ const clipID = 'clip' + message.mid + '_' + nextRandomInt(9999);
svg.dataset.clipID = clipID;
const defs = document.createElementNS("http://www.w3.org/2000/svg", 'defs');
@@ -803,7 +804,7 @@ export function prepareAlbum(options: {
container.append(div);
}
- div.classList.add('album-item');
+ div.classList.add('album-item', 'grouped-item');
div.style.width = (geometry.width / width * 100) + '%';
div.style.height = (geometry.height / height * 100) + '%';
diff --git a/src/helpers/dom.ts b/src/helpers/dom.ts
index 6b78863f..20a8edea 100644
--- a/src/helpers/dom.ts
+++ b/src/helpers/dom.ts
@@ -543,3 +543,50 @@ export const isSelectionSingle = (input: Element = document.activeElement) => {
return single;
};
+
+export const handleScrollSideEvent = (elem: HTMLElement, side: 'top' | 'bottom', callback: () => void, listenerSetter: ListenerSetter) => {
+ if(isTouchSupported) {
+ let lastY: number;
+ const options = {passive: true};
+ listenerSetter.add(elem, 'touchstart', (e) => {
+ if(e.touches.length > 1) {
+ onTouchEnd();
+ return;
+ }
+
+ lastY = e.touches[0].clientY;
+
+ listenerSetter.add(elem, 'touchmove', onTouchMove, options);
+ listenerSetter.add(elem, 'touchend', onTouchEnd, options);
+ }, options);
+
+ const onTouchMove = (e: TouchEvent) => {
+ const clientY = e.touches[0].clientY;
+
+ const isDown = clientY < lastY;
+ if(side == 'bottom' && isDown) callback();
+ else if(side == 'top' && !isDown) callback();
+ lastY = clientY;
+ //alert('isDown: ' + !!isDown);
+ };
+
+ const onTouchEnd = () => {
+ listenerSetter.removeManual(elem, 'touchmove', onTouchMove, options);
+ listenerSetter.removeManual(elem, 'touchend', onTouchEnd, options);
+ };
+ } else {
+ listenerSetter.add(elem, 'wheel', (e) => {
+ const isDown = e.deltaY > 0;
+ //this.log('wheel', e, isDown);
+ if(side == 'bottom' && isDown) callback();
+ else if(side == 'top' && !isDown) callback();
+ }, {passive: true});
+ }
+};
+
+export const getElementByPoint = (container: HTMLElement, verticalSide: 'top' | 'bottom'): HTMLElement => {
+ const rect = container.getBoundingClientRect();
+ const x = Math.ceil(rect.left + ((rect.right - rect.left) / 2) + 1);
+ const y = verticalSide == 'bottom' ? Math.floor(rect.top + rect.height - 1) : Math.ceil(rect.top + 1);
+ return document.elementFromPoint(x, y) as any;
+};
diff --git a/src/helpers/eventListenerBase.ts b/src/helpers/eventListenerBase.ts
index 4e4b7448..15111c7f 100644
--- a/src/helpers/eventListenerBase.ts
+++ b/src/helpers/eventListenerBase.ts
@@ -30,7 +30,8 @@ export default class EventListenerBase) {
+ // * must be protected, but who cares
+ public setListenerResult(name: keyof Listeners, ...args: ArgumentTypes) {
if(this.reuseResults) {
this.listenerResults[name] = args;
}
diff --git a/src/index.hbs b/src/index.hbs
index 38bc85d5..30c27e95 100644
--- a/src/index.hbs
+++ b/src/index.hbs
@@ -157,7 +157,7 @@
@@ -402,7 +402,7 @@
Phone
-