diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index 4e2b42ee..f3bda25c 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -38,7 +38,7 @@ import ListenerSetter from "../../helpers/listenerSetter"; import PollElement from "../poll"; import AudioElement from "../audio"; import { Message, MessageEntity, MessageReplies, MessageReplyHeader } from "../../layer"; -import { DEBUG, MOUNT_CLASS_TO } from "../../lib/mtproto/mtproto_config"; +import { DEBUG, MOUNT_CLASS_TO, REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config"; const IGNORE_ACTIONS = ['messageActionHistoryClear']; @@ -60,7 +60,7 @@ export default class ChatBubbles { //public messagesCount: number = -1; public unreadOut = new Set(); - public needUpdate: {replyMid: number, mid: number}[] = []; // if need wrapSingleMessage + public needUpdate: {replyToPeerId: number, replyMid: number, mid: number}[] = []; // if need wrapSingleMessage public bubbles: {[mid: string]: HTMLDivElement} = {}; public dateMessages: {[timestamp: number]: { @@ -277,17 +277,13 @@ export default class ChatBubbles { this.listenerSetter.add(rootScope, 'messages_downloaded', (e) => { const {peerId, mids} = e.detail; - if(peerId !== this.peerId) { - return; - } - (mids as number[]).forEach(mid => { /* const promise = (this.scrollable.scrollLocked && this.scrollable.scrollLockedPromise) || Promise.resolve(); promise.then(() => { }); */ this.needUpdate.forEachReverse((obj, idx) => { - if(obj.replyMid == mid) { + if(obj.replyMid === mid, obj.replyToPeerId === peerId) { const {mid, replyMid} = this.needUpdate.splice(idx, 1)[0]; //this.log('messages_downloaded', mid, replyMid, i, this.needUpdate, this.needUpdate.length, mids, this.bubbles[mid]); @@ -296,9 +292,9 @@ export default class ChatBubbles { const message = this.chat.getMessage(mid); - const repliedMessage = this.chat.type === 'scheduled' ? this.appMessagesManager.getMessageByPeer(this.peerId, replyMid) : this.chat.getMessage(replyMid); - if(repliedMessage.deleted) { // чтобы не пыталось бесконечно загрузить удалённое сообщение - delete message.reply_to_mid; // WARNING! + const repliedMessage = this.appMessagesManager.getMessageByPeer(obj.replyToPeerId, replyMid); + if(repliedMessage.deleted) { // ! чтобы не пыталось бесконечно загрузить удалённое сообщение + delete message.reply_to_mid; // ! WARNING! } this.renderMessage(message, true, false, bubble, false); @@ -429,9 +425,9 @@ export default class ChatBubbles { } */ //appMessagesManager.readMessages(readed); - /* false && */ this.appMessagesManager.readHistory(this.peerId, max).catch((err: any) => { + /* false && */ this.appMessagesManager.readHistory(this.peerId, max, this.chat.threadId).catch((err: any) => { this.log.error('readHistory err:', err); - this.appMessagesManager.readHistory(this.peerId, max); + this.appMessagesManager.readHistory(this.peerId, max, this.chat.threadId); }); } }); @@ -525,12 +521,23 @@ export default class ChatBubbles { const commentsDiv: HTMLElement = findUpClassName(target, 'replies'); if(commentsDiv) { const bubbleMid = +bubble.dataset.mid; - const message = this.appMessagesManager.filterMessages(this.chat.getMessage(bubbleMid), message => !!(message as Message.message).replies)[0] as Message.message; - const replies = message.replies; - if(replies) { - this.appMessagesManager.getDiscussionMessage(this.peerId, message.mid).then(message => { - this.chat.appImManager.setInnerPeer(-replies.channel_id, (message as MyMessage).mid, 'discussion'); + if(this.peerId === REPLIES_PEER_ID) { + const message = this.chat.getMessage(bubbleMid) as Message.message; + const peerId = this.appPeersManager.getPeerId(message.reply_to.reply_to_peer_id); + const threadId = message.reply_to.reply_to_top_id; + + this.appMessagesManager.wrapSingleMessage(peerId, threadId).then(() => { + this.appMessagesManager.generateThreadServiceStartMessage(this.appMessagesManager.getMessageByPeer(peerId, threadId)); + this.chat.appImManager.setInnerPeer(peerId, message.fwd_from.saved_from_msg_id, 'discussion', threadId); }); + } else { + const message = this.appMessagesManager.filterMessages(this.chat.getMessage(bubbleMid), message => !!(message as Message.message).replies)[0] as Message.message; + const replies = message.replies; + if(replies) { + this.appMessagesManager.getDiscussionMessage(this.peerId, message.mid).then(message => { + this.chat.appImManager.setInnerPeer(-replies.channel_id, undefined, 'discussion', (message as MyMessage).mid); + }); + } } return; @@ -658,14 +665,21 @@ 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'); + const bubbleMid = +bubble.dataset.mid; + this.replyFollowHistory.push(bubbleMid); - if(this.chat.type === 'discussion') { + const message = this.chat.getMessage(bubbleMid) as Message.message; + + const replyToPeerId = message.reply_to.reply_to_peer_id ? this.appPeersManager.getPeerId(message.reply_to.reply_to_peer_id) : this.peerId; + const replyToMid = message.reply_to.reply_to_msg_id; + + this.chat.appImManager.setInnerPeer(replyToPeerId, replyToMid, this.chat.type, this.chat.threadId); + + /* if(this.chat.type === 'discussion') { this.chat.appImManager.setPeer(this.peerId, originalMessageId); } else { this.chat.appImManager.setInnerPeer(this.peerId, originalMessageId); - } + } */ //this.chat.setPeer(this.peerId, originalMessageId); } } else if(target.tagName == 'IMG' && target.parentElement.tagName == "AVATAR-ELEMENT") { @@ -1114,6 +1128,11 @@ export default class ChatBubbles { let topMessage = this.chat.type === 'pinned' ? this.appMessagesManager.pinnedMessages[peerId].maxId : historyStorage.maxId ?? 0; const isTarget = lastMsgId !== undefined; + // * this one will fix topMessage for null message in history (e.g. channel comments with only 1 comment and it is a topMessage) + if(this.chat.type !== 'pinned' && topMessage && !historyStorage.history.includes(topMessage)) { + topMessage = 0; + } + if(!isTarget && topMessage) { const isUnread = this.appMessagesManager.isHistoryUnread(peerId, this.chat.threadId); if(/* dialog.unread_count */isUnread && !samePeer) { @@ -1124,7 +1143,7 @@ export default class ChatBubbles { } } - const isJump = lastMsgId != topMessage; + const isJump = lastMsgId !== topMessage; if(samePeer) { const mounted = this.getMountedBubble(lastMsgId); @@ -1230,7 +1249,7 @@ export default class ChatBubbles { this.lazyLoadQueue.unlock(); //if(dialog && lastMsgID && lastMsgID != topMessage && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) { - if(topMessage && (isTarget || isJump)) { + if((topMessage && isJump) || isTarget) { if(this.scrollable.scrollLocked) { clearTimeout(this.scrollable.scrollLocked); this.scrollable.scrollLocked = 0; @@ -1272,7 +1291,7 @@ export default class ChatBubbles { //if(!this.unreaded.length && dialog) { // lol if(this.scrolledAllDown && topMessage) { // lol - this.appMessagesManager.readHistory(peerId, topMessage); + this.appMessagesManager.readHistory(peerId, topMessage, this.chat.threadId); } if(this.chat.type === 'chat') { @@ -1295,19 +1314,14 @@ export default class ChatBubbles { } public finishPeerChange() { - let peerId = this.peerId; - - //this.topbar.setPeer(peerId); - - const isAnyGroup = this.appPeersManager.isAnyGroup(peerId); + const peerId = this.peerId; const isChannel = this.appPeersManager.isChannel(peerId); - const canWrite = this.appMessagesManager.canWriteToPeer(peerId); this.chatInner.classList.toggle('has-rights', canWrite); this.bubblesContainer.classList.toggle('is-chat-input-hidden', !canWrite); - this.chatInner.classList.toggle('is-chat', isAnyGroup || peerId == rootScope.myId); + this.chatInner.classList.toggle('is-chat', this.chat.isAnyGroup()); this.chatInner.classList.toggle('is-channel', isChannel); } @@ -1536,7 +1550,7 @@ export default class ChatBubbles { this.chat.selection.toggleBubbleCheckbox(bubble, true); } - if(message._ == 'messageService') { + if(message._ === 'messageService') { let action = message.action; let _ = action._; if(IGNORE_ACTIONS.includes(_) || (langPack.hasOwnProperty(_) && !langPack[_])) { @@ -1564,7 +1578,7 @@ export default class ChatBubbles { messageMessage = t.message; //totalEntities = t.entities; totalEntities = t.totalEntities; - } else if(messageMedia?.document?.type != 'sticker') { + } else if(messageMedia?.document?.type !== 'sticker') { messageMessage = message.message; //totalEntities = message.entities; totalEntities = message.totalEntities; @@ -1700,8 +1714,19 @@ export default class ChatBubbles { bubble.classList.add(status); } - let messageWithReplies: Message.message = this.appMessagesManager.filterMessages(message, message => !!(message as Message.message).replies)[0] as any; - const withReplies = messageWithReplies && messageWithReplies.replies && messageWithReplies.replies.pFlags.comments && messageWithReplies.replies.channel_id !== 777; + let messageWithReplies: Message.message; + let withReplies: boolean; + if(this.peerId === REPLIES_PEER_ID) { + messageWithReplies = message; + withReplies = true; + } else { + messageWithReplies = this.appMessagesManager.filterMessages(message, message => !!(message as Message.message).replies)[0] as any; + withReplies = messageWithReplies && messageWithReplies.replies && messageWithReplies.replies.pFlags.comments && messageWithReplies.replies.channel_id !== 777; + } + + if(withReplies) { + bubble.classList.add('with-replies'); + } const isOut = our && (!message.fwd_from || this.peerId != rootScope.myId); let nameContainer = bubbleContainer; @@ -2163,7 +2188,7 @@ export default class ChatBubbles { nameDiv.classList.add('name'); nameDiv.dataset.peerId = message.fwdFromId; - if(this.peerId == rootScope.myId || isForwardFromChannel) { + if(this.peerId === rootScope.myId || this.peerId === REPLIES_PEER_ID || isForwardFromChannel) { nameDiv.style.color = this.appPeersManager.getPeerColorById(message.fwdFromId, false); nameDiv.innerHTML = title; } else { @@ -2175,31 +2200,6 @@ export default class ChatBubbles { nameContainer.append(nameDiv); } } else { - if(message.reply_to_mid && message.reply_to_mid !== this.chat.threadId) { - let originalMessage = this.chat.type === 'scheduled' ? this.appMessagesManager.getMessageByPeer(this.peerId, message.reply_to_mid) : this.chat.getMessage(message.reply_to_mid); - let originalPeerTitle = this.appPeersManager.getPeerTitle(originalMessage.fromId || originalMessage.fwdFromId, true) || ''; - - /////////this.log('message to render reply', originalMessage, originalPeerTitle, bubble, message); - - // need to download separately - if(originalMessage._ == 'messageEmpty') { - //////////this.log('message to render reply empty, need download', message, message.reply_to_mid); - this.appMessagesManager.wrapSingleMessage(this.peerId, message.reply_to_mid); - this.needUpdate.push({replyMid: message.reply_to_mid, mid: message.mid}); - - originalPeerTitle = 'Loading...'; - } - - if(originalMessage.mid) { - bubble.setAttribute('data-original-mid', originalMessage.mid); - } else { - bubble.setAttribute('data-original-mid', message.reply_to_mid); - } - - bubbleContainer.append(wrapReply(originalPeerTitle, originalMessage.message || '', originalMessage)); - bubble.classList.add('is-reply'); - } - if(!bubble.classList.contains('sticker') && needName) { let nameDiv = document.createElement('div'); nameDiv.classList.add('name'); @@ -2215,9 +2215,34 @@ export default class ChatBubbles { bubble.classList.add('hide-name'); } } + + if(message.reply_to_mid && message.reply_to_mid !== this.chat.threadId) { + const replyToPeerId = message.reply_to.reply_to_peer_id ? this.appPeersManager.getPeerId(message.reply_to.reply_to_peer_id) : this.peerId; + + let originalMessage = this.appMessagesManager.getMessageByPeer(replyToPeerId, message.reply_to_mid); + let originalPeerTitle: string; + + /////////this.log('message to render reply', originalMessage, originalPeerTitle, bubble, message); + + // need to download separately + if(originalMessage._ == 'messageEmpty') { + //////////this.log('message to render reply empty, need download', message, message.reply_to_mid); + this.appMessagesManager.wrapSingleMessage(replyToPeerId, message.reply_to_mid); + this.needUpdate.push({replyToPeerId, replyMid: message.reply_to_mid, mid: message.mid}); + + originalPeerTitle = 'Loading...'; + } else { + originalPeerTitle = this.appPeersManager.getPeerTitle(originalMessage.fromId || originalMessage.fwdFromId, true) || ''; + } + + const wrapped = wrapReply(originalPeerTitle, originalMessage.message || '', originalMessage); + bubbleContainer.append(wrapped); + //bubbleContainer.insertBefore(, nameContainer); + bubble.classList.add('is-reply'); + } - if((!our && this.peerId < 0 && (!this.appPeersManager.isChannel(this.peerId) || this.appPeersManager.isMegagroup(this.peerId))) - || (this.peerId == rootScope.myId && !message.reply_to_mid)) { + const needAvatar = this.chat.isAnyGroup() && !isOut; + if(needAvatar) { let avatarElem = new AvatarElement(); //avatarElem.lazyLoadQueue = this.lazyLoadQueue; avatarElem.classList.add('user-avatar', 'avatar-40'); @@ -2226,7 +2251,7 @@ export default class ChatBubbles { avatarElem.setAttribute('peer-title', /* '🔥 FF 🔥' */message.fwd_from.from_name); } - avatarElem.setAttribute('peer', '' + (((message.fwd_from && this.peerId == rootScope.myId) || isForwardFromChannel ? message.fwdFromId : message.fromId) || 0)); + avatarElem.setAttribute('peer', '' + (((message.fwd_from && (this.peerId === rootScope.myId || this.peerId === REPLIES_PEER_ID)) || isForwardFromChannel ? message.fwdFromId : message.fromId) || 0)); avatarElem.update(); //this.log('exec loadDialogPhoto', message); @@ -2245,7 +2270,7 @@ export default class ChatBubbles { bubble.classList.add('is-thread-starter'); } - if(savedFrom) { + if(savedFrom && this.peerId !== REPLIES_PEER_ID) { const goto = document.createElement('div'); goto.classList.add('bubble-beside-button', 'goto-original', 'tgico-arrow-next'); bubbleContainer.append(goto); diff --git a/src/components/chat/chat.ts b/src/components/chat/chat.ts index 3b6d887d..474e0a47 100644 --- a/src/components/chat/chat.ts +++ b/src/components/chat/chat.ts @@ -20,6 +20,7 @@ import ChatContextMenu from "./contextMenu"; import ChatInput from "./input"; import ChatSelection from "./selection"; import ChatTopbar from "./topbar"; +import { REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config"; export type ChatType = 'chat' | 'pinned' | 'replies' | 'discussion' | 'scheduled'; @@ -140,11 +141,6 @@ export default class Chat extends EventListenerBase<{ this.init = null; } - if(this.type === 'discussion' && !this.threadId) { - this.threadId = lastMsgId; - lastMsgId = undefined; - } - //console.time('appImManager setPeer'); //console.time('appImManager setPeer pre promise'); ////console.time('appImManager: pre render start'); @@ -228,4 +224,8 @@ export default class Chat extends EventListenerBase<{ public getMidsByMid(mid: number) { return this.appMessagesManager.getMidsByMessage(this.getMessage(mid)); } + + public isAnyGroup() { + return this.peerId === rootScope.myId || this.peerId === REPLIES_PEER_ID || this.appPeersManager.isAnyGroup(this.peerId); + } } \ No newline at end of file diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index ee312bd3..90d24225 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -1066,9 +1066,9 @@ export default class ChatInput { public onMessageSent(clearInput = true, clearReply?: boolean) { if(this.chat.type !== 'scheduled') { - let dialog = this.appMessagesManager.getDialogByPeerId(this.chat.peerId)[0]; - if(dialog && dialog.top_message) { - this.appMessagesManager.readHistory(this.chat.peerId, dialog.top_message); // lol + const historyStorage = this.appMessagesManager.getHistoryStorage(this.chat.peerId, this.chat.threadId); + if(historyStorage.maxId) { + this.appMessagesManager.readHistory(this.chat.peerId, historyStorage.maxId, this.chat.threadId); // lol } } diff --git a/src/components/chat/replies.ts b/src/components/chat/replies.ts index 3942e8be..c4dc8720 100644 --- a/src/components/chat/replies.ts +++ b/src/components/chat/replies.ts @@ -5,9 +5,11 @@ import appPeersManager from "../../lib/appManagers/appPeersManager"; import rootScope from "../../lib/rootScope"; import { ripple } from "../ripple"; +const TAG_NAME = 'replies-element'; + rootScope.on('replies_updated', (e) => { const message = e.detail; - (Array.from(document.querySelectorAll(`replies-footer-element[data-post-key="${message.peerId}_${message.mid}"]`)) as RepliesElement[]).forEach(element => { + (Array.from(document.querySelectorAll(TAG_NAME + `[data-post-key="${message.peerId}_${message.mid}"]`)) as RepliesElement[]).forEach(element => { element.message = message; element.render(); }); @@ -34,12 +36,12 @@ export default class RepliesElement extends HTMLElement { if(this.type === 'footer') { let leftHTML = '', lastStyle = ''; - if(replies.recent_repliers) { + if(replies?.recent_repliers) { leftHTML += ''; } else { @@ -47,17 +49,21 @@ export default class RepliesElement extends HTMLElement { } let text: string; - if(replies.replies) { - text = replies.replies + ' ' + (replies.replies > 1 ? 'Comments' : 'Comment'); + if(replies) { + if(replies.replies) { + text = replies.replies + ' ' + (replies.replies > 1 ? 'Comments' : 'Comment'); + } else { + text = 'Leave a Comment'; + } } else { - text = 'Leave a Comment'; + text = 'View in chat'; } - - const historyStorage = appMessagesManager.getHistoryStorage(-replies.channel_id); - if(replies.read_max_id < replies.max_id && (!historyStorage.readMaxId || historyStorage.readMaxId < replies.max_id)) { - this.classList.add('is-unread'); + + if(replies) { + const historyStorage = appMessagesManager.getHistoryStorage(-replies.channel_id); + this.classList.toggle('is-unread', replies.read_max_id < replies.max_id && (!historyStorage.readMaxId || historyStorage.readMaxId < replies.max_id)); } - + this.innerHTML = `${leftHTML}${text}`; const rippleContainer = document.createElement('div'); @@ -65,10 +71,10 @@ export default class RepliesElement extends HTMLElement { ripple(rippleContainer); } else { this.classList.add('bubble-beside-button'); - this.innerHTML = `${replies.replies ? formatNumber(replies.replies, 0) : ''}`; + this.innerHTML = `${replies?.replies ? formatNumber(replies.replies, 0) : ''}`; } - if(!this.updated) { + if(replies && !this.updated) { appMessagesManager.subscribeRepliesThread(this.message.peerId, this.message.mid); appMessagesManager.updateMessage(this.message.peerId, this.message.mid, 'replies_updated'); this.updated = true; @@ -76,4 +82,4 @@ export default class RepliesElement extends HTMLElement { } } -customElements.define('replies-element', RepliesElement); \ No newline at end of file +customElements.define(TAG_NAME, RepliesElement); \ No newline at end of file diff --git a/src/components/chat/topbar.ts b/src/components/chat/topbar.ts index 8f10384b..8c7e101b 100644 --- a/src/components/chat/topbar.ts +++ b/src/components/chat/topbar.ts @@ -195,12 +195,12 @@ export default class ChatTopbar { }]; this.btnSearch = ButtonIcon('search'); - this.listenerSetter.add(this.btnSearch, 'click', (e) => { + attachClickEvent(this.btnSearch, (e) => { cancelEvent(e); if(this.peerId) { this.appSidebarRight.searchTab.open(this.peerId, this.chat.threadId); } - }); + }, {listenerSetter: this.listenerSetter}); } public constructPeerHelpers() { @@ -220,15 +220,15 @@ export default class ChatTopbar { this.btnPinned = ButtonIcon('pinlist'); this.btnMute = ButtonIcon('mute'); - this.listenerSetter.add(this.btnPinned, 'click', (e) => { + attachClickEvent(this.btnPinned, (e) => { cancelEvent(e); this.openPinned(true); - }); + }, {listenerSetter: this.listenerSetter}); - this.listenerSetter.add(this.btnMute, 'click', (e) => { + attachClickEvent(this.btnMute, (e) => { cancelEvent(e); this.appMessagesManager.mutePeer(this.peerId); - }); + }, {listenerSetter: this.listenerSetter}); attachClickEvent(this.btnJoin, (e) => { cancelEvent(e); diff --git a/src/lib/appManagers/apiUpdatesManager.ts b/src/lib/appManagers/apiUpdatesManager.ts index 6cc4a13c..12667128 100644 --- a/src/lib/appManagers/apiUpdatesManager.ts +++ b/src/lib/appManagers/apiUpdatesManager.ts @@ -25,6 +25,8 @@ type UpdatesState = { lastPtsUpdateTime?: number }; +const SYNC_DELAY = 25; + export class ApiUpdatesManager { public updatesState: UpdatesState = { pendingPtsUpdates: [], @@ -464,7 +466,7 @@ export class ApiUpdatesManager { } else { this.getDifference(); } - }, 5000) + }, SYNC_DELAY) } } @@ -502,7 +504,7 @@ export class ApiUpdatesManager { curState.syncPending = { timeout: window.setTimeout(() => { this.getDifference(); - }, 5000) + }, SYNC_DELAY) } } diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 4d46afa2..c8e1efb0 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -548,7 +548,7 @@ export class AppImManager { } } - public setInnerPeer(peerId: number, lastMsgId?: number, type: ChatType = 'chat') { + public setInnerPeer(peerId: number, lastMsgId?: number, type: ChatType = 'chat', threadId?: number) { // * prevent opening already opened peer const existingIndex = this.chats.findIndex(chat => chat.peerId == peerId && chat.type == type); if(existingIndex !== -1) { @@ -560,6 +560,10 @@ export class AppImManager { if(type) { this.chat.setType(type); + + if(threadId) { + this.chat.threadId = threadId; + } } //this.chatsSelectTab(this.chat.container); @@ -593,7 +597,7 @@ export class AppImManager { return subtitle; //} - } else if(!appUsersManager.isBot(peerId)) { // user + } else { // user const user = appUsersManager.getUser(peerId); if(rootScope.myId == peerId) { @@ -601,17 +605,19 @@ export class AppImManager { } else if(user) { subtitle = appUsersManager.getUserStatusString(user.id); - const typings = appChatsManager.typingsInPeer[peerId]; - if(typings && typings.length) { - return 'typing...'; - } else if(subtitle == 'online') { - return `${subtitle}`; + if(!appUsersManager.isBot(peerId)) { + const typings = appChatsManager.typingsInPeer[peerId]; + if(typings && typings.length) { + return 'typing...'; + } else if(subtitle == 'online') { + return `${subtitle}`; + } else { + return subtitle; + } } else { return subtitle; } } - } else { - return 'bot'; } } } diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index c045ecc2..5b3dce60 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -41,10 +41,9 @@ const APITIMEOUT = 0; export type HistoryStorage = { count: number | null, history: number[], - pending: number[], - + maxId?: number, - readPromise?: Promise, + readPromise?: Promise, readMaxId?: number, readOutboxMaxId?: number, @@ -262,7 +261,7 @@ export class AppMessagesManager { const processDialog = (dialog: MTDialog.dialog) => { const historyStorage = this.getHistoryStorage(dialog.peerId); - const history = [].concat(historyStorage.pending, historyStorage.history); + const history = [].concat(historyStorage.history); dialog = copy(dialog); let removeUnread = 0; for(const mid of history) { @@ -1287,7 +1286,6 @@ export class AppMessagesManager { } } else { const historyStorage = this.getHistoryStorage(peerId); - //historyStorage.pending.unshift(messageId); historyStorage.history.unshift(messageId); if(!options.isGroupedItem) { @@ -1377,7 +1375,7 @@ export class AppMessagesManager { if(pendingData) { const {peerId, tempId, storage} = pendingData; const historyStorage = this.getHistoryStorage(peerId); - const pos = historyStorage.pending.indexOf(tempId); + const pos = historyStorage.history.indexOf(tempId); apiUpdatesManager.processUpdateMessage({ _: 'updateShort', @@ -1388,7 +1386,7 @@ export class AppMessagesManager { }); if(pos !== -1) { - historyStorage.pending.splice(pos, 1); + historyStorage.history.splice(pos, 1); } delete this.pendingByRandomId[randomId]; @@ -2009,7 +2007,7 @@ export class AppMessagesManager { } const dialog = this.getDialogByPeerId(peerId)[0]; - if(dialog && mid > 0) { + if(dialog && mid) { if(mid > dialog[message.pFlags.out ? 'read_outbox_max_id' : 'read_inbox_max_id']) { @@ -2053,7 +2051,7 @@ export class AppMessagesManager { if(fwdHeader.saved_from_peer && fwdHeader.saved_from_msg_id) { const savedFromPeerId = appPeersManager.getPeerId(fwdHeader.saved_from_peer); //const savedFromMid = fwdHeader.saved_from_msg_id; - const savedFromMid = this.generateMessageId(fwdHeader.saved_from_msg_id); + const savedFromMid = fwdHeader.saved_from_msg_id = this.generateMessageId(fwdHeader.saved_from_msg_id); message.savedFrom = savedFromPeerId + '_' + savedFromMid; } @@ -2664,22 +2662,22 @@ export class AppMessagesManager { dialog.peerId = peerId; // Because we saved message without dialog present - if(mid > 0) { + if(message.pFlags.is_outgoing) { if(mid > dialog[message.pFlags.out ? 'read_outbox_max_id' : 'read_inbox_max_id']) message.pFlags.unread = true; else delete message.pFlags.unread; } let historyStorage = this.getHistoryStorage(peerId); if(historyStorage === undefined/* && !message.deleted */) { // warning - historyStorage[mid > 0 ? 'history' : 'pending'].push(mid); + historyStorage.history.push(mid); /* if(mid < 0 && message.pFlags.unread) { dialog.unread_count++; } */ if(this.mergeReplyKeyboard(historyStorage, message)) { rootScope.broadcast('history_reply_markup', {peerId}); } - } else if(!historyStorage.history.length && !historyStorage.pending.length) { - historyStorage[mid > 0 ? 'history' : 'pending'].push(mid); + } else if(!historyStorage.history.length) { + historyStorage.history.push(mid); } historyStorage.maxId = mid; @@ -3056,6 +3054,27 @@ export class AppMessagesManager { this.getDiscussionMessage(peerId, mid); } + public generateThreadServiceStartMessage(message: Message.message) { + const threadKey = message.peerId + '_' + message.mid; + if(this.threadsServiceMessagesIdsStorage[threadKey]) return; + + const serviceStartMessage: Message.messageService = { + _: 'messageService', + id: this.generateMessageId(message.id, true), + date: message.date, + from_id: message.from_id, + peer_id: message.peer_id, + action: { + _: 'messageActionCustomAction', + message: 'Discussion started' + }, + reply_to: this.generateReplyHeader(message.id) + }; + + this.saveMessages([serviceStartMessage], {isOutgoing: true}); + this.threadsServiceMessagesIdsStorage[threadKey] = serviceStartMessage.mid; + } + public getDiscussionMessage(peerId: number, mid: number) { return apiManager.invokeApi('messages.getDiscussionMessage', { peer: appPeersManager.getInputPeerById(peerId), @@ -3068,25 +3087,7 @@ export class AppMessagesManager { const message = this.filterMessages(result.messages[0], message => !!(message as Message.message).replies)[0] as Message.message; const threadKey = message.peerId + '_' + message.mid; - if(!this.threadsServiceMessagesIdsStorage[threadKey]) { - (result.messages as Message.message[]).forEach(message => { - const serviceStartMessage: Message.messageService = { - _: 'messageService', - id: this.generateMessageId(message.id, true), - date: message.date, - from_id: message.from_id, - peer_id: message.peer_id, - action: { - _: 'messageActionCustomAction', - message: 'Discussion started' - }, - reply_to: this.generateReplyHeader(message.id) - }; - - this.saveMessages([serviceStartMessage], {isOutgoing: true}); - this.threadsServiceMessagesIdsStorage[threadKey] = serviceStartMessage.mid; - }); - } + this.generateThreadServiceStartMessage(message); const historyStorage = this.getHistoryStorage(message.peerId, message.mid); result.max_id = historyStorage.maxId = this.generateMessageId(result.max_id) || 0; @@ -3202,13 +3203,13 @@ export class AppMessagesManager { return promise; } - public readHistory(peerId: number, maxId = 0) { - return Promise.resolve(true); + public readHistory(peerId: number, maxId = 0, threadId?: number) { + // return Promise.resolve(); // console.trace('start read') - if(!this.isHistoryUnread(peerId)) return Promise.resolve(true); + if(!this.isHistoryUnread(peerId, threadId)) return Promise.resolve(); const isChannel = appPeersManager.isChannel(peerId); - const historyStorage = this.getHistoryStorage(peerId); + const historyStorage = this.getHistoryStorage(peerId, threadId); if(!historyStorage.readMaxId || maxId > historyStorage.readMaxId) { historyStorage.readMaxId = maxId; @@ -3218,8 +3219,24 @@ export class AppMessagesManager { return historyStorage.readPromise; } - let apiPromise: Promise; - if(isChannel) { + let apiPromise: Promise; + if(threadId) { + apiPromise = apiManager.invokeApi('messages.readDiscussion', { + peer: appPeersManager.getInputPeerById(peerId), + msg_id: this.getLocalMessageId(threadId), + read_max_id: this.getLocalMessageId(maxId) + }).then((res) => { + apiUpdatesManager.processUpdateMessage({ + _: 'updateShort', + update: { + _: 'updateReadChannelDiscussionInbox', + channel_id: -peerId, + top_msg_id: threadId, + read_max_id: maxId + } as Update.updateReadChannelDiscussionInbox + }); + }); + } else if(isChannel) { apiPromise = apiManager.invokeApi('channels.readHistory', { channel: appChatsManager.getChannelInput(-peerId), max_id: this.getLocalMessageId(maxId) @@ -3232,8 +3249,6 @@ export class AppMessagesManager { channel_id: -peerId } }); - - return res; }); } else { apiPromise = apiManager.invokeApi('messages.readHistory', { @@ -3257,8 +3272,6 @@ export class AppMessagesManager { peer: appPeersManager.getOutputPeer(peerId) } }); - - return true; }); } @@ -3311,10 +3324,10 @@ export class AppMessagesManager { if(threadId) { //threadId = this.getLocalMessageId(threadId); if(!this.threadsStorage[peerId]) this.threadsStorage[peerId] = {}; - return this.threadsStorage[peerId][threadId] ?? (this.threadsStorage[peerId][threadId] = {count: null, history: [], pending: []}); + return this.threadsStorage[peerId][threadId] ?? (this.threadsStorage[peerId][threadId] = {count: null, history: []}); } - return this.historiesStorage[peerId] ?? (this.historiesStorage[peerId] = {count: null, history: [], pending: []}); + return this.historiesStorage[peerId] ?? (this.historiesStorage[peerId] = {count: null, history: []}); } public handleUpdate(update: Update) { @@ -3331,9 +3344,9 @@ export class AppMessagesManager { const message = this.getMessageFromStorage(storage, mid); if(!message.deleted) { const historyStorage = this.getHistoryStorage(peerId); - const pos = historyStorage.pending.indexOf(tempId); + const pos = historyStorage.history.indexOf(tempId); if(pos !== -1) { - historyStorage.pending.splice(pos, 1); + historyStorage.history.splice(pos, 1); } this.finalizePendingMessageCallbacks(storage, tempId, mid); @@ -3376,19 +3389,19 @@ export class AppMessagesManager { const historyStorage = this.getHistoryStorage(peerId); this.updateMessageRepliesIfNeeded(message); - const history = message.mid > 0 ? historyStorage.history : historyStorage.pending; + const history = historyStorage.history; if(history.indexOf(message.mid) !== -1) { return false; } const topMsgId = history[0]; history.unshift(message.mid); - if(message.mid > 0 && message.mid < topMsgId) { + if(message.mid < topMsgId) { history.sort((a, b) => { return b - a; }); } - if(message.mid > 0 && historyStorage.count !== null) { + if(historyStorage.count !== null) { historyStorage.count++; } @@ -3655,6 +3668,14 @@ export class AppMessagesManager { appUsersManager.forceUserOnline(peerId); } + if(threadId) { + const repliesKey = this.threadsToReplies[peerId + '_' + threadId]; + if(repliesKey) { + const [peerId, mid] = repliesKey.split('_').map(n => +n); + this.updateMessage(peerId, mid, 'replies_updated'); + } + } + for(let i = 0, length = history.length; i < length; i++) { const messageId = history[i]; if(messageId > maxId) { @@ -3713,6 +3734,17 @@ export class AppMessagesManager { if(foundAffected) { rootScope.broadcast('messages_read'); } + + if(!threadId && channelId) { + const threadKeyPart = peerId + '_'; + for(const threadKey in this.threadsToReplies) { + if(threadKey.indexOf(threadKeyPart) === 0) { + const [peerId, mid] = this.threadsToReplies[threadKey].split('_').map(n => +n); + rootScope.broadcast('replies_updated', this.getMessageByPeer(peerId, mid)); + } + } + } + break; } @@ -3764,7 +3796,6 @@ export class AppMessagesManager { const historyStorage = this.getHistoryStorage(peerId); //if(historyStorage !== undefined) { const newHistory = historyStorage.history.filter(mid => !historyUpdated.msgs[mid]); - const newPending = historyStorage.pending.filter(mid => !historyUpdated.msgs[mid]); historyStorage.history = newHistory; if(historyUpdated.count && historyStorage.count !== null && @@ -3775,8 +3806,6 @@ export class AppMessagesManager { } } - historyStorage.pending = newPending; - rootScope.broadcast('history_delete', {peerId, msgs: historyUpdated.msgs}); //} @@ -4108,10 +4137,13 @@ export class AppMessagesManager { } public canWriteToPeer(peerId: number) { - const isChannel = appPeersManager.isChannel(peerId); - const hasRights = isChannel && appChatsManager.hasRights(-peerId, 'send'); - - return (!isChannel || hasRights) && (peerId < 0 || appUsersManager.canSendToUser(peerId)); + if(peerId < 0) { + const isChannel = appPeersManager.isChannel(peerId); + const hasRights = isChannel && appChatsManager.hasRights(-peerId, 'send'); + return !isChannel || hasRights; + } else { + return appUsersManager.canSendToUser(peerId); + } } public finalizePendingMessage(randomId: string, finalMessage: any) { @@ -4123,9 +4155,9 @@ export class AppMessagesManager { const historyStorage = this.getHistoryStorage(peerId); // this.log('pending', randomID, historyStorage.pending) - const pos = historyStorage.pending.indexOf(tempId); + const pos = historyStorage.history.indexOf(tempId); if(pos !== -1) { - historyStorage.pending.splice(pos, 1); + historyStorage.history.splice(pos, 1); } const message = this.getMessageFromStorage(storage, tempId); diff --git a/src/lib/appManagers/appUsersManager.ts b/src/lib/appManagers/appUsersManager.ts index 99b60c13..4b6eebd8 100644 --- a/src/lib/appManagers/appUsersManager.ts +++ b/src/lib/appManagers/appUsersManager.ts @@ -4,7 +4,7 @@ import { safeReplaceObject, isObject } from "../../helpers/object"; import { InputUser, Update, User as MTUser, UserStatus } from "../../layer"; //import apiManager from '../mtproto/apiManager'; import apiManager from '../mtproto/mtprotoworker'; -import { MOUNT_CLASS_TO } from "../mtproto/mtproto_config"; +import { MOUNT_CLASS_TO, REPLIES_PEER_ID } from "../mtproto/mtproto_config"; import serverTimeManager from "../mtproto/serverTimeManager"; import { RichTextProcessor } from "../richtextprocessor"; import rootScope from "../rootScope"; @@ -343,6 +343,13 @@ export class AppUsersManager { } public getUserStatusString(userId: number) { + switch(userId) { + case REPLIES_PEER_ID: + return 'reply notifications'; + case 777000: + return 'service notifications'; + } + if(this.isBot(userId)) { return 'bot'; } @@ -351,6 +358,10 @@ export class AppUsersManager { if(!user) { return ''; } + + if(user.pFlags.support) { + return 'support'; + } let str = ''; switch(user.status?._) { @@ -430,7 +441,7 @@ export class AppUsersManager { public canSendToUser(id: number) { const user = this.getUser(id); - return !user.pFlags.deleted; + return !user.pFlags.deleted && user.username !== 'replies'; } public getUserPhoto(id: number) { diff --git a/src/lib/mtproto/mtproto_config.ts b/src/lib/mtproto/mtproto_config.ts index 957b9e89..92b419c4 100644 --- a/src/lib/mtproto/mtproto_config.ts +++ b/src/lib/mtproto/mtproto_config.ts @@ -1,14 +1,6 @@ export type UserAuth = number; -/* - - IMPORTANT NOTICE - ================ - - Do not publish your Webogram fork with my app credentials (below), or your application may be blocked. - You can get your own api_id, api_hash at https://my.telegram.org, see manual at https://core.telegram.org/api/obtaining_api_id. - -*/ +export const REPLIES_PEER_ID = 1271266957; export const App = { id: 1025907, diff --git a/src/scss/partials/_avatar.scss b/src/scss/partials/_avatar.scss index bfab519c..f38acd5c 100644 --- a/src/scss/partials/_avatar.scss +++ b/src/scss/partials/_avatar.scss @@ -15,7 +15,7 @@ avatar-element { text-transform: uppercase; &.tgico-savedmessages:before { - font-size: calc(26px / var(--multiplier)); + font-size: calc(25px / var(--multiplier)); } &.tgico-avatar_deletedaccount:before { @@ -103,6 +103,11 @@ avatar-element { --multiplier: 1.35; } + &.avatar-34 { + --size: 34px; + --multiplier: 1.588235; + } + &.avatar-32 { --size: 32px; --multiplier: 1.6875; diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index bf46fc50..6413e8c8 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -1005,6 +1005,10 @@ $bubble-margin: .25rem; position: relative !important; height: 0px !important; visibility: hidden !important; + + .inner { + visibility: hidden !important; + } } &.is-multiple-documents { @@ -1219,6 +1223,7 @@ $bubble-margin: .25rem; .time { margin-left: 0; color: #fff; + visibility: visible; display: flex; align-items: center; padding: 0 2.5px; @@ -1233,13 +1238,22 @@ $bubble-margin: .25rem; } } + &.with-replies:not(.sticker) .message { + bottom: 55px; + } + &.sticker .message { bottom: 0; } } + + &.with-replies .attachment { + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + } .time { - color: transparent; + visibility: hidden; // * can't use color transparent here, because in name can be emoji font-size: 12px; user-select: none; line-height: 1; @@ -1277,6 +1291,7 @@ $bubble-margin: .25rem; padding: inherit; white-space: nowrap; height: 12px; // * as font-size + visibility: visible; } .tgico-pinnedchat:before { @@ -1349,6 +1364,7 @@ $bubble-margin: .25rem; max-width: 100%; overflow: hidden; text-overflow: ellipsis; + order: 1; //width: max-content; //white-space: nowrap; } @@ -1462,8 +1478,8 @@ $bubble-margin: .25rem; } &-footer { - height: 50px; - border-top: 1px solid #dadce0; + height: 51px; + border-top: 2px solid #e6e7ea; position: relative; display: flex; align-items: center; @@ -1479,9 +1495,11 @@ $bubble-margin: .25rem; &-text { font-weight: 500; - margin-left: 13px; + font-size: 15px; + margin-left: 9px; display: flex; align-items: center; + color: #1f88e3; } &-avatars { @@ -1496,7 +1514,7 @@ $bubble-margin: .25rem; .tgico-next { position: absolute; - right: .5rem; + right: 4px; } &.is-unread { diff --git a/src/scss/partials/_chatTopbar.scss b/src/scss/partials/_chatTopbar.scss index 1f538ece..99a34751 100644 --- a/src/scss/partials/_chatTopbar.scss +++ b/src/scss/partials/_chatTopbar.scss @@ -211,4 +211,18 @@ margin-bottom: .25rem; } } + + .chat:not(.type-chat) & { + .content { + padding-left: 16px; + } + + /* .user-title { + font-size: 20px; + } + + .sidebar-close-button:before { + margin-top: 1px; + } */ + } } \ No newline at end of file