Browse Source

Discussion read, handle reading update

Replies bot
User subtitles
master
Eduard Kuzmenko 4 years ago
parent
commit
3d81994cee
  1. 155
      src/components/chat/bubbles.ts
  2. 10
      src/components/chat/chat.ts
  3. 6
      src/components/chat/input.ts
  4. 36
      src/components/chat/replies.ts
  5. 12
      src/components/chat/topbar.ts
  6. 6
      src/lib/appManagers/apiUpdatesManager.ts
  7. 24
      src/lib/appManagers/appImManager.ts
  8. 148
      src/lib/appManagers/appMessagesManager.ts
  9. 15
      src/lib/appManagers/appUsersManager.ts
  10. 10
      src/lib/mtproto/mtproto_config.ts
  11. 7
      src/scss/partials/_avatar.scss
  12. 28
      src/scss/partials/_chatBubble.scss
  13. 14
      src/scss/partials/_chatTopbar.scss

155
src/components/chat/bubbles.ts

@ -38,7 +38,7 @@ import ListenerSetter from "../../helpers/listenerSetter"; @@ -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 { @@ -60,7 +60,7 @@ export default class ChatBubbles {
//public messagesCount: number = -1;
public unreadOut = new Set<number>();
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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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);

10
src/components/chat/chat.ts

@ -20,6 +20,7 @@ import ChatContextMenu from "./contextMenu"; @@ -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<{ @@ -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<{ @@ -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);
}
}

6
src/components/chat/input.ts

@ -1066,9 +1066,9 @@ export default class ChatInput { @@ -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
}
}

36
src/components/chat/replies.ts

@ -5,9 +5,11 @@ import appPeersManager from "../../lib/appManagers/appPeersManager"; @@ -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 { @@ -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 += '<div class="replies-footer-avatars">'
let l: string[] = [];
replies.recent_repliers/* .slice().reverse() */.forEach((peer, idx) => {
lastStyle = idx == 0 ? '' : `style="transform: translateX(-${idx * 12}px);"`;
l.push(`<avatar-element class="avatar-32" dialog="0" peer="${appPeersManager.getPeerId(peer)}" ${lastStyle}></avatar-element>`);
lastStyle = idx == 0 ? '' : `style="transform: translateX(-${idx * 14}px);"`;
l.push(`<avatar-element class="avatar-34" dialog="0" peer="${appPeersManager.getPeerId(peer)}" ${lastStyle}></avatar-element>`);
});
leftHTML += l.reverse().join('') + '</div>';
} else {
@ -47,17 +49,21 @@ export default class RepliesElement extends HTMLElement { @@ -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}<span class="replies-footer-text" ${lastStyle}>${text}</span><span class="tgico-next"></span>`;
const rippleContainer = document.createElement('div');
@ -65,10 +71,10 @@ export default class RepliesElement extends HTMLElement { @@ -65,10 +71,10 @@ export default class RepliesElement extends HTMLElement {
ripple(rippleContainer);
} else {
this.classList.add('bubble-beside-button');
this.innerHTML = `<span class="tgico-commentssticker"></span><span class="replies-beside-text">${replies.replies ? formatNumber(replies.replies, 0) : ''}</span>`;
this.innerHTML = `<span class="tgico-commentssticker"></span><span class="replies-beside-text">${replies?.replies ? formatNumber(replies.replies, 0) : ''}</span>`;
}
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 { @@ -76,4 +82,4 @@ export default class RepliesElement extends HTMLElement {
}
}
customElements.define('replies-element', RepliesElement);
customElements.define(TAG_NAME, RepliesElement);

12
src/components/chat/topbar.ts

@ -195,12 +195,12 @@ export default class ChatTopbar { @@ -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 { @@ -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);

6
src/lib/appManagers/apiUpdatesManager.ts

@ -25,6 +25,8 @@ type UpdatesState = { @@ -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 { @@ -464,7 +466,7 @@ export class ApiUpdatesManager {
} else {
this.getDifference();
}
}, 5000)
}, SYNC_DELAY)
}
}
@ -502,7 +504,7 @@ export class ApiUpdatesManager { @@ -502,7 +504,7 @@ export class ApiUpdatesManager {
curState.syncPending = {
timeout: window.setTimeout(() => {
this.getDifference();
}, 5000)
}, SYNC_DELAY)
}
}

24
src/lib/appManagers/appImManager.ts

@ -548,7 +548,7 @@ export class AppImManager { @@ -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 { @@ -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 { @@ -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 { @@ -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 '<span class="online">typing...</span>';
} else if(subtitle == 'online') {
return `<span class="online">${subtitle}</span>`;
if(!appUsersManager.isBot(peerId)) {
const typings = appChatsManager.typingsInPeer[peerId];
if(typings && typings.length) {
return '<span class="online">typing...</span>';
} else if(subtitle == 'online') {
return `<span class="online">${subtitle}</span>`;
} else {
return subtitle;
}
} else {
return subtitle;
}
}
} else {
return 'bot';
}
}
}

148
src/lib/appManagers/appMessagesManager.ts

@ -41,10 +41,9 @@ const APITIMEOUT = 0; @@ -41,10 +41,9 @@ const APITIMEOUT = 0;
export type HistoryStorage = {
count: number | null,
history: number[],
pending: number[],
maxId?: number,
readPromise?: Promise<boolean>,
readPromise?: Promise<void>,
readMaxId?: number,
readOutboxMaxId?: number,
@ -262,7 +261,7 @@ export class AppMessagesManager { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -3218,8 +3219,24 @@ export class AppMessagesManager {
return historyStorage.readPromise;
}
let apiPromise: Promise<boolean>;
if(isChannel) {
let apiPromise: Promise<void>;
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 { @@ -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 { @@ -3257,8 +3272,6 @@ export class AppMessagesManager {
peer: appPeersManager.getOutputPeer(peerId)
}
});
return true;
});
}
@ -3311,10 +3324,10 @@ export class AppMessagesManager { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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);

15
src/lib/appManagers/appUsersManager.ts

@ -4,7 +4,7 @@ import { safeReplaceObject, isObject } from "../../helpers/object"; @@ -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 { @@ -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 { @@ -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 { @@ -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) {

10
src/lib/mtproto/mtproto_config.ts

@ -1,14 +1,6 @@ @@ -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,

7
src/scss/partials/_avatar.scss

@ -15,7 +15,7 @@ avatar-element { @@ -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 { @@ -103,6 +103,11 @@ avatar-element {
--multiplier: 1.35;
}
&.avatar-34 {
--size: 34px;
--multiplier: 1.588235;
}
&.avatar-32 {
--size: 32px;
--multiplier: 1.6875;

28
src/scss/partials/_chatBubble.scss

@ -1005,6 +1005,10 @@ $bubble-margin: .25rem; @@ -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; @@ -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; @@ -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; @@ -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; @@ -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; @@ -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; @@ -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; @@ -1496,7 +1514,7 @@ $bubble-margin: .25rem;
.tgico-next {
position: absolute;
right: .5rem;
right: 4px;
}
&.is-unread {

14
src/scss/partials/_chatTopbar.scss

@ -211,4 +211,18 @@ @@ -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;
} */
}
}
Loading…
Cancel
Save