From 552611ce6cebcd0cf01dac086f3b67cca6e5cbac Mon Sep 17 00:00:00 2001 From: morethanwords Date: Tue, 10 Aug 2021 19:52:12 +0300 Subject: [PATCH] Increment channel post views Add missing comment translations --- src/components/chat/bubbles.ts | 75 ++++++++++++++++++-- src/components/chat/messageRender.ts | 2 +- src/components/chat/replies.ts | 12 ++-- src/lang.ts | 7 ++ src/layer.d.ts | 7 +- src/lib/appManagers/appMessagesManager.ts | 42 ++++++++--- src/lib/langPack.ts | 1 + src/lib/rootScope.ts | 2 +- src/scripts/in/schema_additional_params.json | 7 +- 9 files changed, 131 insertions(+), 24 deletions(-) diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index d07d9ddb..9800b3ac 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -71,6 +71,8 @@ import whichChild from "../../helpers/dom/whichChild"; import { cancelAnimationByKey } from "../../helpers/animation"; import assumeType from "../../helpers/assumeType"; import { EmoticonsDropdown } from "../emoticonsDropdown"; +import debounce from "../../helpers/schedulers/debounce"; +import { formatNumber } from "../../helpers/number"; const USE_MEDIA_TAILS = false; const IGNORE_ACTIONS: Set = new Set([ @@ -162,6 +164,10 @@ export default class ChatBubbles { private resolveLadderAnimation: () => Promise; private emptyPlaceholderMid: number; + private viewsObserver: IntersectionObserver; + private viewsMids: Set = new Set(); + private sendViewCountersDebounced: () => Promise; + constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appStickersManager: AppStickersManager, @@ -580,6 +586,27 @@ export default class ChatBubbles { } }); + this.listenerSetter.add(rootScope)('message_views', (e) => { + if(this.peerId !== e.peerId) return; + + fastRaf(() => { + const bubble = this.bubbles[e.mid]; + if(!bubble) return; + + const postViewsElements = Array.from(bubble.querySelectorAll('.post-views')) as HTMLElement[]; + if(postViewsElements.length) { + const str = formatNumber(e.views, 1); + let different = false; + postViewsElements.forEach(postViews => { + if(different || postViews.innerHTML !== str) { + different = true; + postViews.innerHTML = str; + } + }); + } + }); + }); + this.unreadedObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if(entry.isIntersecting) { @@ -590,6 +617,23 @@ export default class ChatBubbles { }); }); + this.viewsObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if(entry.isIntersecting) { + this.viewsMids.add(+(entry.target as HTMLElement).dataset.mid); + this.viewsObserver.unobserve(entry.target); + this.sendViewCountersDebounced(); + } + }); + }); + + this.sendViewCountersDebounced = debounce(() => { + const mids = [...this.viewsMids]; + this.viewsMids.clear(); + + this.appMessagesManager.incrementMessageViews(this.peerId, mids); + }, 1000, false, true); + if('ResizeObserver' in window) { let wasHeight = this.scrollable.container.offsetHeight; let resizing = false; @@ -1291,6 +1335,10 @@ export default class ChatBubbles { this.unreadedObserver.unobserve(bubble); this.unreaded.delete(bubble); } + if(this.viewsObserver) { + this.viewsObserver.unobserve(bubble); + this.viewsMids.delete(mid); + } //this.unreaded.findAndSplice(mid => mid === id); bubble.remove(); //bubble.remove(); @@ -1515,10 +1563,12 @@ export default class ChatBubbles { this.lazyLoadQueue.clear(); this.unreadedObserver && this.unreadedObserver.disconnect(); + this.viewsObserver && this.viewsObserver.disconnect(); this.stickyIntersector && this.stickyIntersector.disconnect(); delete this.lazyLoadQueue; this.unreadedObserver && delete this.unreadedObserver; + this.viewsObserver && delete this.viewsObserver; this.stickyIntersector && delete this.stickyIntersector; } @@ -1569,6 +1619,11 @@ export default class ChatBubbles { this.unreadedSeen.clear(); this.readPromise = undefined; } + + if(this.viewsObserver) { + this.viewsObserver.disconnect(); + this.viewsMids.clear(); + } this.loadedTopTimes = this.loadedBottomTimes = 0; @@ -2012,7 +2067,11 @@ export default class ChatBubbles { } } - this.bubbleGroups.addBubble(bubble, message, reverse); + if(message._ === 'message') { + this.bubbleGroups.addBubble(bubble, message, reverse); + } else { + bubble.classList.add('is-group-first', 'is-group-last'); + } } public getMiddleware() { @@ -2227,6 +2286,10 @@ export default class ChatBubbles { bubbleContainer.prepend(messageDiv); //bubble.prepend(timeSpan, messageDiv); // that's bad + if(message.views && !message.pFlags.is_outgoing && this.viewsObserver) { + this.viewsObserver.observe(bubble); + } + if(message.reply_markup && message.reply_markup._ === 'replyInlineMarkup' && message.reply_markup.rows && message.reply_markup.rows.length) { const rows = (message.reply_markup as ReplyMarkup.replyKeyboardMarkup).rows; @@ -2421,9 +2484,9 @@ export default class ChatBubbles { case 'messageMediaWebPage': { processingWebPage = true; - let webpage: WebPage.webPage | WebPage.webPageEmpty = messageMedia.webpage; + let webpage: WebPage = messageMedia.webpage; ////////this.log('messageMediaWebPage', webpage); - if(webpage._ === 'webPageEmpty') { + if(webpage._ !== 'webPage') { break; } @@ -2445,10 +2508,8 @@ export default class ChatBubbles { previewResizer.append(preview); } - let doc: any = null; - if(webpage.document) { - doc = webpage.document; - + const doc = webpage.document as MyDocument; + if(doc) { if(doc.type === 'gif' || doc.type === 'video') { //if(doc.size <= 20e6) { bubble.classList.add('video'); diff --git a/src/components/chat/messageRender.ts b/src/components/chat/messageRender.ts index 3e0def0a..dde236e5 100644 --- a/src/components/chat/messageRender.ts +++ b/src/components/chat/messageRender.ts @@ -27,7 +27,7 @@ export namespace MessageRender { const postAuthor = message.post_author || message.fwd_from?.post_author; bubble.classList.add('channel-post'); - time = formatNumber(message.views, 1) + ' ' + (postAuthor ? RichTextProcessor.wrapEmojiText(postAuthor) + ', ' : '') + time; + time = '' + formatNumber(message.views, 1) + ' ' + (postAuthor ? RichTextProcessor.wrapEmojiText(postAuthor) + ', ' : '') + time; if(!message.fwd_from?.saved_from_msg_id && chat.type !== 'pinned') { const forward = document.createElement('div'); diff --git a/src/components/chat/replies.ts b/src/components/chat/replies.ts index 7ec013e3..02d4790c 100644 --- a/src/components/chat/replies.ts +++ b/src/components/chat/replies.ts @@ -12,6 +12,8 @@ import appPeersManager from "../../lib/appManagers/appPeersManager"; import rootScope from "../../lib/rootScope"; import { ripple } from "../ripple"; import AvatarElement from "../avatar"; +import { i18n } from "../../lib/langPack"; +import replaceContent from "../../helpers/dom/replaceContent"; const TAG_NAME = 'replies-element'; @@ -103,15 +105,15 @@ export default class RepliesElement extends HTMLElement { this.append(leftPart); } - let text: string; + let text: HTMLElement; if(replies) { if(replies.replies) { - text = replies.replies + ' ' + (replies.replies > 1 ? 'Comments' : 'Comment'); + text = i18n('Comments', [replies.replies]); } else { - text = 'Leave a Comment'; + text = i18n('LeaveAComment'); } } else { - text = 'View in chat'; + text = i18n('ViewInChat'); } if(replies) { @@ -141,7 +143,7 @@ export default class RepliesElement extends HTMLElement { this.append(textSpan, iconSpan, rippleContainer); } - textSpan.innerHTML = text; + replaceContent(textSpan, text); } else { this.classList.add('bubble-beside-button'); this.innerHTML = `${replies?.replies ? formatNumber(replies.replies, 0) : ''}`; diff --git a/src/lang.ts b/src/lang.ts index 756a8e80..f2693953 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -183,6 +183,7 @@ const lang = { "ChannelMegaJoined": "You joined this group", "Channel.DescriptionPlaceholder": "Description (optional)", "DescriptionPlaceholder": "Description", + "DiscussionStarted": "Discussion started", "Draft": "Draft", "FilterAlwaysShow": "Include Chats", "FilterNeverShow": "Exclude Chats", @@ -211,6 +212,10 @@ const lang = { "one_value": "%1$d channel", "other_value": "%1$d channels" }, + "Comments": { + "one_value": "%1$d Comment", + "other_value": "%1$d Comments" + }, "Groups": { "one_value": "%1$d group", "other_value": "%1$d groups" @@ -544,6 +549,8 @@ const lang = { "Send": "Send", "ChannelJoin": "JOIN", "Yesterday": "yesterday", + "LeaveAComment": "Leave a comment", + "ViewInChat": "View in chat", // * macos "AccountSettings.Filters": "Chat Folders", diff --git a/src/layer.d.ts b/src/layer.d.ts index 338a1fcf..16e82ba4 100644 --- a/src/layer.d.ts +++ b/src/layer.d.ts @@ -996,7 +996,7 @@ export namespace MessageMedia { /** * @link https://core.telegram.org/type/MessageAction */ -export type MessageAction = MessageAction.messageActionEmpty | MessageAction.messageActionChatCreate | MessageAction.messageActionChatEditTitle | MessageAction.messageActionChatEditPhoto | MessageAction.messageActionChatDeletePhoto | MessageAction.messageActionChatAddUser | MessageAction.messageActionChatDeleteUser | MessageAction.messageActionChatJoinedByLink | MessageAction.messageActionChannelCreate | MessageAction.messageActionChatMigrateTo | MessageAction.messageActionChannelMigrateFrom | MessageAction.messageActionPinMessage | MessageAction.messageActionHistoryClear | MessageAction.messageActionGameScore | MessageAction.messageActionPaymentSentMe | MessageAction.messageActionPaymentSent | MessageAction.messageActionPhoneCall | MessageAction.messageActionScreenshotTaken | MessageAction.messageActionCustomAction | MessageAction.messageActionBotAllowed | MessageAction.messageActionSecureValuesSentMe | MessageAction.messageActionSecureValuesSent | MessageAction.messageActionContactSignUp | MessageAction.messageActionGeoProximityReached | MessageAction.messageActionGroupCall | MessageAction.messageActionInviteToGroupCall | MessageAction.messageActionSetMessagesTTL | MessageAction.messageActionGroupCallScheduled | MessageAction.messageActionChatLeave | MessageAction.messageActionChannelDeletePhoto | MessageAction.messageActionChannelEditTitle | MessageAction.messageActionChannelEditPhoto | MessageAction.messageActionChannelEditVideo | MessageAction.messageActionChatEditVideo | MessageAction.messageActionChatAddUsers | MessageAction.messageActionChatJoined | MessageAction.messageActionChatReturn | MessageAction.messageActionChatJoinedYou | MessageAction.messageActionChatReturnYou; +export type MessageAction = MessageAction.messageActionEmpty | MessageAction.messageActionChatCreate | MessageAction.messageActionChatEditTitle | MessageAction.messageActionChatEditPhoto | MessageAction.messageActionChatDeletePhoto | MessageAction.messageActionChatAddUser | MessageAction.messageActionChatDeleteUser | MessageAction.messageActionChatJoinedByLink | MessageAction.messageActionChannelCreate | MessageAction.messageActionChatMigrateTo | MessageAction.messageActionChannelMigrateFrom | MessageAction.messageActionPinMessage | MessageAction.messageActionHistoryClear | MessageAction.messageActionGameScore | MessageAction.messageActionPaymentSentMe | MessageAction.messageActionPaymentSent | MessageAction.messageActionPhoneCall | MessageAction.messageActionScreenshotTaken | MessageAction.messageActionCustomAction | MessageAction.messageActionBotAllowed | MessageAction.messageActionSecureValuesSentMe | MessageAction.messageActionSecureValuesSent | MessageAction.messageActionContactSignUp | MessageAction.messageActionGeoProximityReached | MessageAction.messageActionGroupCall | MessageAction.messageActionInviteToGroupCall | MessageAction.messageActionSetMessagesTTL | MessageAction.messageActionGroupCallScheduled | MessageAction.messageActionDiscussionStarted | MessageAction.messageActionChatLeave | MessageAction.messageActionChannelDeletePhoto | MessageAction.messageActionChannelEditTitle | MessageAction.messageActionChannelEditPhoto | MessageAction.messageActionChannelEditVideo | MessageAction.messageActionChatEditVideo | MessageAction.messageActionChatAddUsers | MessageAction.messageActionChatJoined | MessageAction.messageActionChatReturn | MessageAction.messageActionChatJoinedYou | MessageAction.messageActionChatReturnYou; export namespace MessageAction { export type messageActionEmpty = { @@ -1156,6 +1156,10 @@ export namespace MessageAction { schedule_date: number }; + export type messageActionDiscussionStarted = { + _: 'messageActionDiscussionStarted' + }; + export type messageActionChatLeave = { _: 'messageActionChatLeave', user_id?: number @@ -9822,6 +9826,7 @@ export interface ConstructorDeclMap { 'messageEntityHighlight': MessageEntity.messageEntityHighlight, 'messageEntityLinebreak': MessageEntity.messageEntityLinebreak, 'messageEntityCaret': MessageEntity.messageEntityCaret, + 'messageActionDiscussionStarted': MessageAction.messageActionDiscussionStarted, 'messageActionChatLeave': MessageAction.messageActionChatLeave, 'messageActionChannelDeletePhoto': MessageAction.messageActionChannelDeletePhoto, 'messageActionChannelEditTitle': MessageAction.messageActionChannelEditTitle, diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 2b9d0ea1..21526925 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -120,9 +120,6 @@ type PendingAfterMsg = Partial; export class AppMessagesManager { - private static MESSAGE_ID_INCREMENT = 0x10000; - private static MESSAGE_ID_OFFSET = 0xFFFFFFFF; - private messagesStorageByPeerId: {[peerId: string]: MessagesStorage}; public groupedMessagesStorage: {[groupId: string]: MessagesStorage}; // will be used for albums private scheduledMessagesStorage: {[peerId: string]: MessagesStorage}; @@ -3463,8 +3460,7 @@ export class AppMessagesManager { from_id: {_: 'peerUser', user_id: 0}/* message.from_id */, peer_id: message.peer_id, action: { - _: 'messageActionCustomAction', - message: 'Discussion started' + _: 'messageActionDiscussionStarted' }, reply_to: this.generateReplyHeader(message.id) }; @@ -4280,12 +4276,12 @@ export class AppMessagesManager { private onUpdateChannelMessageViews = (update: Update.updateChannelMessageViews) => { const views = update.views; - //const mid = update.id; + const peerId = -update.channel_id; const mid = appMessagesIdsManager.generateMessageId(update.id); - const message = this.getMessageByPeer(-update.channel_id, mid); + const message: Message.message = this.getMessageByPeer(peerId, mid); if(!message.deleted && message.views && message.views < views) { message.views = views; - rootScope.dispatchEvent('message_views', {mid, views}); + rootScope.dispatchEvent('message_views', {peerId, mid, views}); } }; @@ -4627,6 +4623,36 @@ export class AppMessagesManager { }); } + public incrementMessageViews(peerId: number, mids: number[]) { + if(!mids.length) { + return; + } + + return apiManager.invokeApiSingle('messages.getMessagesViews', { + peer: appPeersManager.getInputPeerById(peerId), + id: mids.map(mid => appMessagesIdsManager.getServerMessageId(mid)), + increment: true + }).then(views => { + const updates: Update[] = new Array(mids.length); + const channelId = -peerId; + for(let i = 0, length = mids.length; i < length; ++i) { + updates[i] = { + _: 'updateChannelMessageViews', + channel_id: channelId, + id: mids[i], + views: views.views[i].views + }; + } + + apiUpdatesManager.processUpdateMessage({ + _: 'updates', + updates, + chats: views.chats, + users: views.users + }); + }); + } + private notifyAboutMessage(message: MyMessage, options: Partial<{ fwdCount: number, peerTypeNotifySettings: PeerNotifySettings diff --git a/src/lib/langPack.ts b/src/lib/langPack.ts index 4b7c1b83..19577972 100644 --- a/src/lib/langPack.ts +++ b/src/lib/langPack.ts @@ -41,6 +41,7 @@ export const langPack: {[actionType: string]: LangPackKey} = { "messageActionChannelEditVideo": "Chat.Service.Channel.UpdatedVideo", "messageActionChannelDeletePhoto": "Chat.Service.Channel.RemovedPhoto", "messageActionHistoryClear": "HistoryCleared", + "messageActionDiscussionStarted": "DiscussionStarted", "messageActionChannelMigrateFrom": "ActionMigrateFromGroup", diff --git a/src/lib/rootScope.ts b/src/lib/rootScope.ts index 5ab348be..e9f66c48 100644 --- a/src/lib/rootScope.ts +++ b/src/lib/rootScope.ts @@ -57,7 +57,7 @@ export type BroadcastEvents = { //'history_request': void, 'message_edit': {storage: MessagesStorage, peerId: number, mid: number}, - 'message_views': {mid: number, views: number}, + 'message_views': {peerId: number, mid: number, views: number}, 'message_sent': {storage: MessagesStorage, tempId: number, tempMessage: any, mid: number}, 'messages_pending': void, 'messages_read': void, diff --git a/src/scripts/in/schema_additional_params.json b/src/scripts/in/schema_additional_params.json index ce27dd02..d8579c57 100644 --- a/src/scripts/in/schema_additional_params.json +++ b/src/scripts/in/schema_additional_params.json @@ -56,7 +56,8 @@ {"name": "clear_history", "type": "boolean"}, {"name": "pending", "type": "boolean"}, {"name": "error", "type": "any"}, - {"name": "send", "type": "() => Promise"} + {"name": "send", "type": "() => Promise"}, + {"name": "totalEntities", "type": "MessageEntity[]"} ] }, { "predicate": "messageService", @@ -152,6 +153,10 @@ "params": [ {"name": "initials", "type": "string"} ] +}, { + "predicate": "messageActionDiscussionStarted", + "params": [], + "type": "MessageAction" }, { "predicate": "messageActionChatLeave", "params": [