From a25170edf4c081c86ddc51b164bb266083e448b3 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Tue, 22 Dec 2020 09:07:37 +0200 Subject: [PATCH] Search, pinned message, date follow in discussions Fix time css again --- src/components/appSearch.ts | 10 ++- src/components/avatar.ts | 25 +++++- src/components/chat/bubbles.ts | 6 +- src/components/chat/chat.ts | 4 + src/components/chat/messageRender.ts | 4 +- src/components/chat/pinnedMessage.ts | 13 ++- src/components/chat/search.ts | 2 +- src/components/chat/topbar.ts | 95 ++++++++++++---------- src/components/lazyLoadQueue.ts | 5 ++ src/components/sidebarRight/tabs/search.ts | 11 ++- src/lib/appManagers/appMessagesManager.ts | 8 +- src/lib/rootScope.ts | 8 +- src/scss/partials/_chatBubble.scss | 13 ++- 13 files changed, 137 insertions(+), 67 deletions(-) diff --git a/src/components/appSearch.ts b/src/components/appSearch.ts index b0030244..17b1a574 100644 --- a/src/components/appSearch.ts +++ b/src/components/appSearch.ts @@ -77,6 +77,7 @@ export default class AppSearch { public listsContainer: HTMLDivElement = null; private peerId = 0; // 0 - means global + private threadId = 0; private scrollable: Scrollable; @@ -119,6 +120,7 @@ export default class AppSearch { this.searchInput.value = ''; this.query = ''; this.peerId = 0; + this.threadId = 0; } this.minMsgId = 0; @@ -134,10 +136,14 @@ export default class AppSearch { this.searchPromise = null; } - public beginSearch(peerId?: number) { + public beginSearch(peerId?: number, threadId?: number) { if(peerId) { this.peerId = peerId; } + + if(threadId) { + this.threadId = threadId; + } this.searchInput.input.focus(); } @@ -240,7 +246,7 @@ export default class AppSearch { }); } - return this.searchPromise = appMessagesManager.getSearch(this.peerId, query, {_: 'inputMessagesFilterEmpty'}, maxId, 20, this.offsetRate).then(res => { + return this.searchPromise = appMessagesManager.getSearch(this.peerId, query, {_: 'inputMessagesFilterEmpty'}, maxId, 20, this.offsetRate, undefined, this.threadId).then(res => { this.searchPromise = null; if(this.searchInput.value != query) { diff --git a/src/components/avatar.ts b/src/components/avatar.ts index e8a902e3..67000434 100644 --- a/src/components/avatar.ts +++ b/src/components/avatar.ts @@ -4,6 +4,7 @@ import rootScope from "../lib/rootScope"; import { attachClickEvent, cancelEvent } from "../helpers/dom"; import AppMediaViewer, { AppMediaViewerAvatar } from "./appMediaViewer"; import { Photo } from "../layer"; +import type { LazyLoadQueueIntersector } from "./lazyLoadQueue"; rootScope.on('avatar_update', (e) => { let peerId = e.detail; @@ -19,6 +20,8 @@ export default class AvatarElement extends HTMLElement { private peerId: number; private isDialog = false; public peerTitle: string; + public lazyLoadQueue: LazyLoadQueueIntersector; + private addedToQueue = false; constructor() { super(); @@ -91,10 +94,13 @@ export default class AvatarElement extends HTMLElement { } } - //disconnectedCallback() { + disconnectedCallback() { // браузер вызывает этот метод при удалении элемента из документа // (может вызываться много раз, если элемент многократно добавляется/удаляется) - //} + if(this.lazyLoadQueue) { + this.lazyLoadQueue.unobserve(this); + } + } static get observedAttributes(): string[] { return ['peer', 'dialog', 'peer-title'/* массив имён атрибутов для отслеживания их изменений */]; @@ -118,7 +124,20 @@ export default class AvatarElement extends HTMLElement { } public update() { - appProfileManager.putPhoto(this, this.peerId, this.isDialog, this.peerTitle); + if(this.lazyLoadQueue) { + if(this.addedToQueue) return; + this.lazyLoadQueue.push({ + div: this, + load: () => { + return appProfileManager.putPhoto(this, this.peerId, this.isDialog, this.peerTitle).finally(() => { + this.addedToQueue = false; + }); + } + }); + this.addedToQueue = true; + } else { + appProfileManager.putPhoto(this, this.peerId, this.isDialog, this.peerTitle); + } } } diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index c89d1867..425ac998 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -1701,7 +1701,7 @@ export default class ChatBubbles { bubble.classList.add(status); } - const withReplyFooter = message.replies && message.replies.pFlags.comments; + const withReplyFooter = message.replies && message.replies.pFlags.comments && message.replies.channel_id !== 777; const isOut = our && (!message.fwd_from || this.peerId != rootScope.myId); let nameContainer = bubbleContainer; @@ -2087,6 +2087,7 @@ export default class ChatBubbles { `; const avatarElem = new AvatarElement(); + //avatarElem.lazyLoadQueue = this.lazyLoadQueue; avatarElem.setAttribute('peer', '' + message.media.user_id); avatarElem.classList.add('contact-avatar', 'avatar-54'); @@ -2218,6 +2219,7 @@ export default class ChatBubbles { if((!our && this.peerId < 0 && (!this.appPeersManager.isChannel(this.peerId) || this.appPeersManager.isMegagroup(this.peerId))) || (this.peerId == rootScope.myId && !message.reply_to_mid)) { let avatarElem = new AvatarElement(); + //avatarElem.lazyLoadQueue = this.lazyLoadQueue; avatarElem.classList.add('user-avatar', 'avatar-40'); if(!message.fwdFromId && message.fwd_from && message.fwd_from.from_name) { @@ -2376,7 +2378,7 @@ export default class ChatBubbles { onDatePick = (timestamp: number) => { const peerId = this.peerId; - this.appMessagesManager.requestHistory(peerId, 0, 2, -1, timestamp).then(history => { + this.appMessagesManager.requestHistory(peerId, 0, 2, -1, timestamp, this.chat.threadId).then(history => { if(!history?.messages?.length) { this.log.error('no history!'); return; diff --git a/src/components/chat/chat.ts b/src/components/chat/chat.ts index 317d1d7c..3b6d887d 100644 --- a/src/components/chat/chat.ts +++ b/src/components/chat/chat.ts @@ -79,9 +79,13 @@ export default class Chat extends EventListenerBase<{ this.contextMenu = new ChatContextMenu(this.bubbles.bubblesContainer, this, this.appMessagesManager, this.appChatsManager, this.appPeersManager, this.appPollsManager); if(this.type === 'chat') { + this.topbar.constructUtils(); this.topbar.constructPeerHelpers(); } else if(this.type === 'pinned') { this.topbar.constructPinnedHelpers(); + } else if(this.type === 'discussion') { + this.topbar.constructUtils(); + this.topbar.constructDiscussionHelpers(); } this.topbar.construct(); diff --git a/src/components/chat/messageRender.ts b/src/components/chat/messageRender.ts index 71bd2ac3..7fd9539b 100644 --- a/src/components/chat/messageRender.ts +++ b/src/components/chat/messageRender.ts @@ -21,7 +21,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.savedFrom) { const forward = document.createElement('div'); @@ -45,7 +45,7 @@ export namespace MessageRender { if(chat.type != 'pinned' && message.pFlags.pinned) { bubble.classList.add('is-pinned'); - time = '' + time; + time = '' + time; } const title = getFullDate(date) diff --git a/src/components/chat/pinnedMessage.ts b/src/components/chat/pinnedMessage.ts index 211a466c..ab7aab7a 100644 --- a/src/components/chat/pinnedMessage.ts +++ b/src/components/chat/pinnedMessage.ts @@ -240,6 +240,8 @@ export default class ChatPinnedMessage { public btnOpen: HTMLButtonElement; public setPinnedMessage: () => void; + + private isStatic = false; constructor(private topbar: ChatTopbar, private chat: Chat, private appMessagesManager: AppMessagesManager, private appPeersManager: AppPeersManager) { this.listenerSetter = new ListenerSetter(); @@ -309,6 +311,8 @@ export default class ChatPinnedMessage { // * 200 - no lags // * 100 - need test this.setPinnedMessage = debounce(() => this._setPinnedMessage(), 100, true, true); + + this.isStatic = this.chat.type === 'discussion'; } public destroy() { @@ -319,6 +323,7 @@ export default class ChatPinnedMessage { } public setCorrectIndex(lastScrollDirection?: number) { + if(this.isStatic) return; //return; if(this.locked || this.hidden/* || this.chat.setPeerPromise || this.chat.bubbles.messagesQueuePromise */) { @@ -340,16 +345,18 @@ export default class ChatPinnedMessage { const mid = el.dataset.mid; if(el && mid !== undefined) { - this.chat.log('[PM]: setCorrectIndex will test mid:', mid); + //this.chat.log('[PM]: setCorrectIndex will test mid:', mid); this.testMid(+mid, lastScrollDirection); } } public testMid(mid: number, lastScrollDirection?: number) { + if(this.isStatic) return; + //if(lastScrollDirection !== undefined) return; if(this.hidden) return; - this.chat.log('[PM]: testMid', mid); + //this.chat.log('[PM]: testMid', mid); let currentIndex: number = this.mids.findIndex(_mid => _mid <= mid); if(currentIndex !== -1 && !this.isNeededMore(currentIndex)) { @@ -371,7 +378,7 @@ export default class ChatPinnedMessage { currentIndex = 0; } */ - this.chat.log('[PM]: testMid: pinned currentIndex', currentIndex, mid); + //this.chat.log('[PM]: testMid: pinned currentIndex', currentIndex, mid); const changed = this.pinnedIndex != currentIndex; if(changed) { diff --git a/src/components/chat/search.ts b/src/components/chat/search.ts index 78541887..178f0fd8 100644 --- a/src/components/chat/search.ts +++ b/src/components/chat/search.ts @@ -73,7 +73,7 @@ export default class ChatSearch { this.selectResult(this.searchGroup.list.children[0] as HTMLElement); } }); - this.appSearch.beginSearch(this.chat.peerId); + this.appSearch.beginSearch(this.chat.peerId, this.chat.threadId); //appImManager.topbar.parentElement.insertBefore(this.results, appImManager.bubblesContainer); this.chat.bubbles.bubblesContainer.append(this.results); diff --git a/src/components/chat/topbar.ts b/src/components/chat/topbar.ts index c734c3ea..8f10384b 100644 --- a/src/components/chat/topbar.ts +++ b/src/components/chat/topbar.ts @@ -144,20 +144,7 @@ export default class ChatTopbar { }, {listenerSetter: this.listenerSetter}); } - public constructPeerHelpers() { - this.avatarElement = new AvatarElement(); - this.avatarElement.setAttribute('dialog', '1'); - this.avatarElement.setAttribute('clickable', ''); - this.avatarElement.classList.add('avatar-40', 'person-avatar'); - - this.subtitle = document.createElement('div'); - this.subtitle.classList.add('info'); - - this.pinnedMessage = new ChatPinnedMessage(this, this.chat, this.appMessagesManager, this.appPeersManager); - - this.btnJoin = Button('btn-primary chat-join hide'); - this.btnJoin.append('SUBSCRIBE'); - + public constructUtils() { this.menuButtons = [{ icon: 'search', text: 'Search', @@ -176,14 +163,14 @@ export default class ChatTopbar { onClick: () => { this.appMessagesManager.mutePeer(this.peerId); }, - verify: () => rootScope.myId != this.peerId && !this.appMessagesManager.isPeerMuted(this.peerId) + verify: () => this.chat.type === 'chat' && rootScope.myId != this.peerId && !this.appMessagesManager.isPeerMuted(this.peerId) }, { icon: 'unmute', text: 'Unmute', onClick: () => { this.appMessagesManager.mutePeer(this.peerId); }, - verify: () => rootScope.myId != this.peerId && this.appMessagesManager.isPeerMuted(this.peerId) + verify: () => this.chat.type === 'chat' && rootScope.myId != this.peerId && this.appMessagesManager.isPeerMuted(this.peerId) }, { icon: 'select', text: 'Select Messages', @@ -204,25 +191,40 @@ export default class ChatTopbar { onClick: () => { new PopupDeleteDialog(this.peerId); }, - verify: () => !!this.appMessagesManager.getDialogByPeerId(this.peerId)[0] + verify: () => this.chat.type === 'chat' && !!this.appMessagesManager.getDialogByPeerId(this.peerId)[0] }]; + this.btnSearch = ButtonIcon('search'); + this.listenerSetter.add(this.btnSearch, 'click', (e) => { + cancelEvent(e); + if(this.peerId) { + this.appSidebarRight.searchTab.open(this.peerId, this.chat.threadId); + } + }); + } + + public constructPeerHelpers() { + this.avatarElement = new AvatarElement(); + this.avatarElement.setAttribute('dialog', '1'); + this.avatarElement.setAttribute('clickable', ''); + this.avatarElement.classList.add('avatar-40', 'person-avatar'); + + this.subtitle = document.createElement('div'); + this.subtitle.classList.add('info'); + + this.pinnedMessage = new ChatPinnedMessage(this, this.chat, this.appMessagesManager, this.appPeersManager); + + this.btnJoin = Button('btn-primary chat-join hide'); + this.btnJoin.append('SUBSCRIBE'); + this.btnPinned = ButtonIcon('pinlist'); this.btnMute = ButtonIcon('mute'); - this.btnSearch = ButtonIcon('search'); this.listenerSetter.add(this.btnPinned, 'click', (e) => { cancelEvent(e); this.openPinned(true); }); - this.listenerSetter.add(this.btnSearch, 'click', (e) => { - cancelEvent(e); - if(this.peerId) { - this.appSidebarRight.searchTab.open(this.peerId); - } - }); - this.listenerSetter.add(this.btnMute, 'click', (e) => { cancelEvent(e); this.appMessagesManager.mutePeer(this.peerId); @@ -307,6 +309,10 @@ export default class ChatTopbar { } }); } + + public constructDiscussionHelpers() { + this.pinnedMessage = new ChatPinnedMessage(this, this.chat, this.appMessagesManager, this.appPeersManager); + } public openPinned(byCurrent: boolean) { this.chat.appImManager.setInnerPeer(this.peerId, byCurrent ? +this.pinnedMessage.pinnedMessageContainer.divAndCaption.container.dataset.mid : 0, 'pinned'); @@ -360,23 +366,30 @@ export default class ChatTopbar { const middleware = this.chat.bubbles.getMiddleware(); if(this.pinnedMessage) { // * replace with new one - if(this.wasPeerId) { // * change - const newPinnedMessage = new ChatPinnedMessage(this, this.chat, this.appMessagesManager, this.appPeersManager); - this.pinnedMessage.pinnedMessageContainer.divAndCaption.container.replaceWith(newPinnedMessage.pinnedMessageContainer.divAndCaption.container); - this.pinnedMessage.destroy(); - //this.pinnedMessage.pinnedMessageContainer.toggle(true); - this.pinnedMessage = newPinnedMessage; - } - - appStateManager.getState().then((state) => { - if(!middleware()) return; - - this.pinnedMessage.hidden = !!state.hiddenPinnedMessages[peerId]; - - if(!isTarget) { - this.pinnedMessage.setCorrectIndex(0); + if(this.chat.type === 'chat') { + if(this.wasPeerId) { // * change + const newPinnedMessage = new ChatPinnedMessage(this, this.chat, this.appMessagesManager, this.appPeersManager); + this.pinnedMessage.pinnedMessageContainer.divAndCaption.container.replaceWith(newPinnedMessage.pinnedMessageContainer.divAndCaption.container); + this.pinnedMessage.destroy(); + //this.pinnedMessage.pinnedMessageContainer.toggle(true); + this.pinnedMessage = newPinnedMessage; } - }); + + appStateManager.getState().then((state) => { + if(!middleware()) return; + + this.pinnedMessage.hidden = !!state.hiddenPinnedMessages[peerId]; + + if(!isTarget) { + this.pinnedMessage.setCorrectIndex(0); + } + }); + } else if(this.chat.type === 'discussion') { + this.pinnedMessage.pinnedMid = this.chat.threadId; + this.pinnedMessage.count = 1; + this.pinnedMessage.pinnedIndex = 0; + this.pinnedMessage._setPinnedMessage(); + } } window.requestAnimationFrame(() => { diff --git a/src/components/lazyLoadQueue.ts b/src/components/lazyLoadQueue.ts index 389bcb65..a57f7718 100644 --- a/src/components/lazyLoadQueue.ts +++ b/src/components/lazyLoadQueue.ts @@ -199,6 +199,11 @@ export class LazyLoadQueueIntersector extends LazyLoadQueueBase { public unshift(el: LazyLoadElement) { super.unshift(el); } + + public unobserve(el: HTMLElement) { + this.queue.findAndSplice(i => i.div === el); + this.intersector.unobserve(el); + } } export default class LazyLoadQueue extends LazyLoadQueueIntersector { diff --git a/src/components/sidebarRight/tabs/search.ts b/src/components/sidebarRight/tabs/search.ts index cd6955ab..6bca9956 100644 --- a/src/components/sidebarRight/tabs/search.ts +++ b/src/components/sidebarRight/tabs/search.ts @@ -11,13 +11,15 @@ export default class AppPrivateSearchTab implements SliderTab { private appSearch: AppSearch; private peerId = 0; + private threadId = 0; onOpenAfterTimeout() { - this.appSearch.beginSearch(this.peerId); + this.appSearch.beginSearch(this.peerId, this.threadId); } onCloseAfterTimeout() { this.peerId = 0; + this.threadId = 0; this.appSearch.reset(); } @@ -31,18 +33,19 @@ export default class AppPrivateSearchTab implements SliderTab { }); } - open(peerId: number) { + open(peerId: number, threadId?: number) { if(this.init) { this.init(); this.init = null; } - if(this.peerId != 0) { - this.appSearch.beginSearch(this.peerId); + if(this.peerId !== 0) { + this.appSearch.beginSearch(this.peerId, this.threadId); return; } this.peerId = peerId; + this.threadId = threadId; appSidebarRight.selectTab(AppSidebarRight.SLIDERITEMSIDS.search); appSidebarRight.toggleSidebar(true); diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index f281fd1a..de73bff0 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -1493,9 +1493,11 @@ export class AppMessagesManager { if(threadId) { const chatHistoryStorage = this.getHistoryStorage(peerId); const readMaxId = Math.max(chatHistoryStorage.readMaxId, historyStorage.readMaxId); - return readMaxId < historyStorage.maxId; + const message = this.getMessageByPeer(peerId, historyStorage.maxId); + return !message.pFlags.out && readMaxId < historyStorage.maxId; } else { - return (peerId > 0 ? Math.max(historyStorage.readMaxId, historyStorage.readOutboxMaxId) : historyStorage.readMaxId) < historyStorage.maxId; + const message = this.getMessageByPeer(peerId, historyStorage.maxId); + return !message.pFlags.out && (peerId > 0 ? Math.max(historyStorage.readMaxId, historyStorage.readOutboxMaxId) : historyStorage.readMaxId) < historyStorage.maxId; } } @@ -2956,7 +2958,7 @@ export class AppMessagesManager { max_id: 0, min_id: 0, hash: 0, - top_msg_id: threadId + top_msg_id: this.getLocalMessageId(threadId) || 0 }, { //timeout: APITIMEOUT, noErrorBox: true diff --git a/src/lib/rootScope.ts b/src/lib/rootScope.ts index 9ad3b808..8bca794e 100644 --- a/src/lib/rootScope.ts +++ b/src/lib/rootScope.ts @@ -5,7 +5,7 @@ import type { Poll, PollResults } from "./appManagers/appPollsManager"; import type { MyDialogFilter } from "./storages/filters"; import type { ConnectionStatusChange } from "../types"; import type { UserTyping } from "./appManagers/appChatsManager"; -import { MOUNT_CLASS_TO, UserAuth } from "./mtproto/mtproto_config"; +import { DEBUG, MOUNT_CLASS_TO, UserAuth } from "./mtproto/mtproto_config"; type BroadcastEvents = { 'user_update': number, @@ -96,8 +96,10 @@ class RootScope { } public broadcast = (name: T, detail?: BroadcastEvents[T]) => { - /* if(name != 'user_update') { - console.debug('Broadcasting ' + name + ' event, with args:', detail); + /* if(DEBUG) { + if(name != 'user_update') { + console.debug('Broadcasting ' + name + ' event, with args:', detail); + } } */ const myCustomEvent = new CustomEvent(name, {detail}); diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index b3f91edf..767ac027 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -189,7 +189,8 @@ $bubble-margin: .25rem; } */ } - .chat.type-chat & .bubble__container { + .chat.type-chat & .bubble__container, + .chat.type-discussion & .bubble__container { cursor: pointer; pointer-events: all; } @@ -1224,6 +1225,7 @@ $bubble-margin: .25rem; line-height: 18px; pointer-events: all; // show title white-space: nowrap; + height: 18px; .inner { display: none; @@ -1248,10 +1250,14 @@ $bubble-margin: .25rem; cursor: default; /* display: inline-flex; align-items: center; */ + height: 12px; i { font-size: 1.15rem; margin-right: .4rem; + } + + &-icon { margin-left: 2px; } @@ -1270,6 +1276,7 @@ $bubble-margin: .25rem; line-height: 1; padding: inherit; white-space: nowrap; + height: 12px; // * as font-size } .tgico-pinnedchat:before { @@ -1492,7 +1499,7 @@ $bubble-margin: .25rem; } &.is-unread { - .replies-text { + .replies-footer-text { &:after { content: " "; background-color: $color-blue; @@ -1771,7 +1778,7 @@ $bubble-margin: .25rem; .inner { color: $darkgreen; - bottom: 2px; + bottom: 4px; } &:after, .inner:after {