Update replies footer

This commit is contained in:
Eduard Kuzmenko 2020-12-22 05:02:30 +02:00
parent b9d6f916a4
commit 18debfb4a6
8 changed files with 175 additions and 71 deletions

View File

@ -1,7 +1,7 @@
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import appProfileManager from "../lib/appManagers/appProfileManager";
import rootScope from "../lib/rootScope";
import { cancelEvent } from "../helpers/dom";
import { attachClickEvent, cancelEvent } from "../helpers/dom";
import AppMediaViewer, { AppMediaViewerAvatar } from "./appMediaViewer";
import { Photo } from "../layer";
@ -33,7 +33,7 @@ export default class AvatarElement extends HTMLElement {
if(this.getAttribute('clickable') === '') {
this.setAttribute('clickable', 'set');
let loading = false;
this.addEventListener('click', async(e) => {
attachClickEvent(this, async(e) => {
cancelEvent(e);
if(loading) return;
//console.log('avatar clicked');
@ -120,13 +120,6 @@ export default class AvatarElement extends HTMLElement {
public update() {
appProfileManager.putPhoto(this, this.peerId, this.isDialog, this.peerTitle);
}
adoptedCallback() {
// вызывается, когда элемент перемещается в новый документ
// (происходит в document.adoptNode, используется очень редко)
}
// у элемента могут быть ещё другие методы и свойства
}
customElements.define("avatar-element", AvatarElement);

View File

@ -38,6 +38,7 @@ import ListenerSetter from "../../helpers/listenerSetter";
import PollElement from "../poll";
import AudioElement from "../audio";
import { MessageEntity, MessageReplies, MessageReplyHeader } from "../../layer";
import { DEBUG, MOUNT_CLASS_TO } from "../../lib/mtproto/mtproto_config";
const IGNORE_ACTIONS = ['messageActionHistoryClear'];
@ -315,6 +316,17 @@ export default class ChatBubbles {
this.listenerSetter.add(this.bubblesContainer, 'click', this.onBubblesClick/* , {capture: true, passive: false} */);
if(DEBUG) {
this.listenerSetter.add(this.bubblesContainer, 'dblclick', (e) => {
const bubble = findUpClassName(e.target, 'grouped-item') || findUpClassName(e.target, 'bubble');
if(bubble) {
const mid = +bubble.dataset.mid;
this.log('debug message:', this.chat.getMessage(mid));
this.chat.bubbles.highlightBubble(bubble);
}
});
}
this.stickyIntersector = new StickyIntersector(this.scrollable.container, (stuck, target) => {
for(const timestamp in this.dateMessages) {
const dateMessage = this.dateMessages[timestamp];

View File

@ -1,20 +1,19 @@
import { getFullDate } from "../../helpers/date";
import { formatNumber } from "../../helpers/number";
import { MessageReplies } from "../../layer";
import { Message } from "../../layer";
import appMessagesManager from "../../lib/appManagers/appMessagesManager";
import appPeersManager from "../../lib/appManagers/appPeersManager";
import RichTextProcessor from "../../lib/richtextprocessor";
import rootScope from "../../lib/rootScope";
import { ripple } from "../ripple";
import Chat from "./chat";
type Message = any;
export namespace MessageRender {
/* export const setText = () => {
}; */
export const setTime = (chat: Chat, message: Message, bubble: HTMLElement, bubbleContainer: HTMLElement, messageDiv: HTMLElement) => {
export const setTime = (chat: Chat, message: any, bubble: HTMLElement, bubbleContainer: HTMLElement, messageDiv: HTMLElement) => {
const date = new Date(message.date * 1000);
let time = ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2);
@ -69,14 +68,47 @@ export namespace MessageRender {
message: any,
messageDiv: HTMLElement
}) => {
const replies = message.replies as MessageReplies;
const isFooter = !bubble.classList.contains('sticker') && !bubble.classList.contains('emoji-big');
const repliesFooter = new RepliesFooterElement();
if(isFooter) {
const container = document.createElement('div');
container.classList.add('replies-footer');
repliesFooter.message = message;
repliesFooter.type = 'footer';
bubbleContainer.prepend(repliesFooter);
}
};
}
rootScope.on('replies_updated', (e) => {
const message = e.detail;
(Array.from(document.querySelectorAll(`replies-footer-element[data-post-key="${message.peerId}_${message.mid}"]`)) as RepliesFooterElement[]).forEach(element => {
element.message = message;
element.render();
});
});
class RepliesFooterElement extends HTMLElement {
public message: Message.message;
public type: 'footer' | 'beside';
private updated = false;
constructor() {
super();
this.classList.add('replies-footer');
}
connectedCallback() {
this.render();
this.dataset.postKey = this.message.peerId + '_' + this.message.mid;
}
public render() {
const replies = this.message.replies;
let leftHTML = '', lastStyle = '';
if(replies?.recent_repliers) {
if(replies.recent_repliers) {
leftHTML += '<div class="replies-footer-avatars">'
/**
* MACOS, ANDROID - без реверса
@ -94,25 +126,29 @@ export namespace MessageRender {
}
let text: string;
if(replies?.replies) {
if(replies.replies) {
text = replies.replies + ' ' + (replies.replies > 1 ? 'Comments' : 'Comment');
} else {
text = 'Leave a Comment';
}
if(replies) {
const historyStorage = appMessagesManager.getHistoryStorage(-replies.channel_id);
if(replies.read_max_id < replies.max_id && (!historyStorage.readMaxId || historyStorage.readMaxId < replies.max_id)) {
container.classList.add('is-unread');
}
this.classList.add('is-unread');
}
container.innerHTML = `${leftHTML}<span class="replies-footer-text" ${lastStyle}>${text}</span><span class="tgico-next"></span>`;
if(!this.updated) {
appMessagesManager.subscribeRepliesThread(this.message.peerId, this.message.mid);
appMessagesManager.updateMessage(this.message.peerId, this.message.mid, 'replies_updated');
this.updated = true;
}
this.innerHTML = `${leftHTML}<span class="replies-footer-text" ${lastStyle}>${text}</span><span class="tgico-next"></span>`;
const rippleContainer = document.createElement('div');
container.append(rippleContainer);
this.append(rippleContainer);
ripple(rippleContainer);
bubbleContainer.prepend(container);
}
};
}
customElements.define('replies-footer-element', RepliesFooterElement);

View File

@ -7,7 +7,7 @@ import PinnedContainer from "./pinnedContainer";
import PinnedMessageBorder from "./pinnedMessageBorder";
import ReplyContainer, { wrapReplyDivAndCaption } from "./replyContainer";
import rootScope from "../../lib/rootScope";
import { cancelEvent, findUpClassName, getElementByPoint, handleScrollSideEvent } from "../../helpers/dom";
import { attachClickEvent, cancelEvent, findUpClassName, getElementByPoint, handleScrollSideEvent } from "../../helpers/dom";
import Chat from "./chat";
import ListenerSetter from "../../helpers/listenerSetter";
import ButtonIcon from "../buttonIcon";
@ -271,10 +271,10 @@ export default class ChatPinnedMessage {
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) => {
attachClickEvent(this.btnOpen, (e) => {
cancelEvent(e);
this.topbar.openPinned(true);
});
}, {listenerSetter: this.listenerSetter});
this.listenerSetter.add(rootScope, 'peer_pinned_messages', (e) => {
const peerId = e.detail.peerId;

View File

@ -904,7 +904,9 @@ export default class AppSharedMediaTab implements SliderTab {
setText(appPeersManager.getPeerUsername(peerId), this.profileElements.username);
}
this.profileElements.notificationsCheckbox.checked = !appMessagesManager.isPeerMuted(peerId);
const muted = appMessagesManager.isPeerMuted(peerId);
this.profileElements.notificationsCheckbox.checked = !muted;
this.profileElements.notificationsStatus.innerText = muted ? 'Disabled' : 'Enabled';
} else {
window.requestAnimationFrame(() => {
this.profileElements.notificationsRow.style.display = 'none';

View File

@ -19,7 +19,7 @@ import {MyDialogFilter as DialogFilter} from "../storages/filters";
import appPeersManager from './appPeersManager';
import appStateManager from "./appStateManager";
import appUsersManager, { User } from "./appUsersManager";
import { App, MOUNT_CLASS_TO } from "../mtproto/mtproto_config";
import { App, DEBUG, MOUNT_CLASS_TO } from "../mtproto/mtproto_config";
import Button from "../../components/button";
import SetTransition from "../../components/singleTransition";
import AppStorage from '../storage';
@ -877,6 +877,16 @@ export class AppDialogsManager {
}
}, {capture: true});
if(DEBUG) {
list.addEventListener('dblclick', (e) => {
const li = findUpTag(e.target, 'LI');
if(li) {
const peerId = +li.getAttribute('data-peerId');
this.log('debug dialog:', appMessagesManager.getDialogByPeerId(peerId));
}
});
}
if(withContext) {
attachContextMenuListener(list, this.contextMenu.onContextMenu);
}

View File

@ -4,7 +4,7 @@ import { tsNow } from "../../helpers/date";
import { copy, defineNotNumerableProperties, getObjectKeysAndSort } from "../../helpers/object";
import { randomLong } from "../../helpers/random";
import { splitStringByLength, limitSymbols } from "../../helpers/string";
import { Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputNotifyPeer, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, NotifyPeer, PhotoSize, SendMessageAction, Update } from "../../layer";
import { Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputNotifyPeer, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, NotifyPeer, PhotoSize, SendMessageAction, Update } from "../../layer";
import { InvokeApiOptions } from "../../types";
import { langPack } from "../langPack";
import { logger, LogLevels } from "../logger";
@ -110,6 +110,10 @@ export class AppMessagesManager {
} = {};
public pinnedMessages: {[peerId: string]: PinnedStorage} = {};
public threadsToReplies: {
[peerId_threadId: string]: string;
} = {};
public pendingByRandomId: {
[randomId: string]: {
peerId: number,
@ -2399,7 +2403,7 @@ export class AppMessagesManager {
});
}
public markDialogUnread(peerId: number, read?: boolean) {
public markDialogUnread(peerId: number, read?: true) {
const dialog = this.getDialogByPeerId(peerId)[0];
if(!dialog) return Promise.reject();
@ -3022,6 +3026,15 @@ export class AppMessagesManager {
});
}
public subscribeRepliesThread(peerId: number, mid: number) {
const repliesKey = peerId + '_' + mid;
for(const threadKey in this.threadsToReplies) {
if(this.threadsToReplies[threadKey] === repliesKey) return;
}
this.getDiscussionMessage(peerId, mid);
}
public getDiscussionMessage(peerId: number, mid: number) {
return apiManager.invokeApi('messages.getDiscussionMessage', {
peer: appPeersManager.getInputPeerById(peerId),
@ -3037,6 +3050,8 @@ export class AppMessagesManager {
result.read_inbox_max_id = historyStorage.readMaxId = this.generateMessageId(result.read_inbox_max_id) || 0;
result.read_outbox_max_id = historyStorage.readOutboxMaxId = this.generateMessageId(result.read_outbox_max_id) || 0;
this.threadsToReplies[message.peerId + '_' + message.mid] = peerId + '_' + mid;
return result;
});
}
@ -3315,11 +3330,10 @@ export class AppMessagesManager {
// this.log.warn(dT(), 'message unread', message.mid, message.pFlags.unread)
const pendingMessage = this.checkPendingMessage(message);
const historyStorage = this.getHistoryStorage(peerId);
const history = message.mid > 0 ? historyStorage.history : historyStorage.pending;
if(history.indexOf(message.mid) != -1) {
if(history.indexOf(message.mid) !== -1) {
return false;
}
const topMsgId = history[0];
@ -3338,8 +3352,8 @@ export class AppMessagesManager {
rootScope.broadcast('history_reply_markup', {peerId});
}
if(!message.pFlags.out && message.from_id) {
appUsersManager.forceUserOnline(appPeersManager.getPeerId(message.from_id), message.date);
if(message.fromId > 0 && !message.pFlags.out && message.from_id) {
appUsersManager.forceUserOnline(message.fromId, message.date);
}
if(!pendingMessage) {
@ -3353,6 +3367,8 @@ export class AppMessagesManager {
}
}
this.updateMessageRepliesIfNeeded(message);
const dialog = foundDialog[0];
if(dialog) {
const inboxUnread = !message.pFlags.out && message.pFlags.unread;
@ -3950,6 +3966,37 @@ export class AppMessagesManager {
}
}
private updateMessageRepliesIfNeeded(threadMessage: MyMessage) {
try { // * на всякий случай, скорее всего это не понадобится
if(threadMessage.peerId < 0 && threadMessage.reply_to) {
const threadId = threadMessage.reply_to.reply_to_top_id || threadMessage.reply_to.reply_to_msg_id;
const threadKey = threadMessage.peerId + '_' + threadId;
const repliesKey = this.threadsToReplies[threadKey];
if(repliesKey) {
const [peerId, mid] = repliesKey.split('_').map(n => +n);
this.updateMessage(peerId, mid, 'replies_updated');
}
}
} catch(err) {
this.log.error('incrementMessageReplies err', err, threadMessage);
}
}
public updateMessage(peerId: number, mid: number, broadcastEventName?: 'replies_updated'): Promise<Message.message> {
const promise: Promise<Message.message> = this.wrapSingleMessage(peerId, mid, true).then(() => {
const message = this.getMessageByPeer(peerId, mid);
if(broadcastEventName) {
rootScope.broadcast(broadcastEventName, message);
}
return message;
});
return promise;
}
private checkPendingMessage(message: any) {
const randomId = this.pendingByMessageId[message.mid];
let pendingMessage: any;
@ -4474,7 +4521,7 @@ export class AppMessagesManager {
Promise.all(promises).finally(() => {
this.fetchSingleMessagesPromise = null;
if(this.needSingleMessages.length) this.fetchSingleMessages();
if(Object.keys(this.needSingleMessages).length) this.fetchSingleMessages();
resolve();
});
}, 0);
@ -4482,10 +4529,10 @@ export class AppMessagesManager {
}
public wrapSingleMessage(peerId: number, msgId: number, overwrite = false): Promise<void> {
if(this.getMessagesStorage(peerId)[msgId] && !overwrite) {
if(!this.getMessageByPeer(peerId, msgId).deleted && !overwrite) {
rootScope.broadcast('messages_downloaded', {peerId, mids: [msgId]});
return Promise.resolve();
} else if(!this.needSingleMessages[peerId] || this.needSingleMessages[peerId].indexOf(msgId) == -1) {
} else if(!this.needSingleMessages[peerId] || this.needSingleMessages[peerId].indexOf(msgId) === -1) {
(this.needSingleMessages[peerId] ?? (this.needSingleMessages[peerId] = [])).push(msgId);
return this.fetchSingleMessages();
} else if(this.fetchSingleMessagesPromise) {
@ -4531,6 +4578,8 @@ export class AppMessagesManager {
}
}
this.updateMessageRepliesIfNeeded(message);
if(!message.pFlags.out && message.pFlags.unread) {
history.unread++;
}

View File

@ -1,4 +1,4 @@
import type { StickerSet, Update } from "../layer";
import type { Message, StickerSet, Update } from "../layer";
import type { MyDocument } from "./appManagers/appDocsManager";
import type { AppMessagesManager, Dialog, MessagesStorage } from "./appManagers/appMessagesManager";
import type { Poll, PollResults } from "./appManagers/appPollsManager";
@ -46,6 +46,8 @@ type BroadcastEvents = {
'messages_downloaded': {peerId: number, mids: number[]},
'messages_media_read': {peerId: number, mids: number[]},
'replies_updated': Message.message,
'scheduled_new': {peerId: number, mid: number},
'scheduled_delete': {peerId: number, mids: number[]},