From 67d4ef33ba319bcf6137050c4390e21bd9a57473 Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Wed, 1 Sep 2021 20:17:06 +0300 Subject: [PATCH] Mentions --- src/components/appMediaViewer.ts | 2 +- src/components/chat/bubbles.ts | 14 +- src/components/chat/input.ts | 21 +- src/components/singleTransition.ts | 20 +- src/config/app.ts | 2 +- src/helpers/slicedArray.ts | 1 - src/lib/appManagers/appDialogsManager.ts | 81 ++++++-- src/lib/appManagers/appMessagesManager.ts | 239 +++++++++++++++++----- src/scss/partials/_chat.scss | 62 ++++-- src/scss/partials/_chatlist.scss | 89 ++++++-- src/scss/partials/_leftSidebar.scss | 1 + src/scss/partials/_mediaViewer.scss | 3 +- src/scss/partials/_selector.scss | 12 +- 13 files changed, 423 insertions(+), 124 deletions(-) diff --git a/src/components/appMediaViewer.ts b/src/components/appMediaViewer.ts index e00e5e3f..83718de8 100644 --- a/src/components/appMediaViewer.ts +++ b/src/components/appMediaViewer.ts @@ -703,7 +703,7 @@ class AppMediaViewerBase 1) { + if(closing && zoomValue !== 1) { // const width = this.moversContainer.scrollWidth * scaleX; // const height = this.moversContainer.scrollHeight * scaleY; const willBeLeft = windowSize.windowW / 2 - rect.width / 2; diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index c4a955fe..e7032f05 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -772,6 +772,16 @@ export default class ChatBubbles { } }); + const readContents: number[] = []; + for(const mid of this.unreadedSeen) { + const message: MyMessage = this.chat.getMessage(mid); + if(message.pFlags.media_unread) { + readContents.push(mid); + } + } + + this.appMessagesManager.readMessages(this.peerId, readContents); + this.unreadedSeen.clear(); if(DEBUG) { @@ -2156,7 +2166,9 @@ export default class ChatBubbles { if(!our && !message.pFlags.out && this.unreadedObserver) { //this.log('not our message', message, message.pFlags.unread); - const isUnread = message.pFlags.unread || (this.historyStorage.readMaxId !== undefined && this.historyStorage.readMaxId < message.mid); + const isUnread = message.pFlags.unread || + (message.pFlags.media_unread && message.pFlags.mentioned) || + (this.historyStorage.readMaxId !== undefined && this.historyStorage.readMaxId < message.mid); if(isUnread) { this.unreadedObserver.observe(bubble); this.unreaded.set(bubble, message.mid); diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index eac7cd37..3f171a30 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -160,6 +160,8 @@ export default class ChatInput { private goDownBtn: HTMLButtonElement; private goDownUnreadBadge: HTMLElement; + private goMentionBtn: HTMLButtonElement; + private goMentionUnreadBadge: HTMLSpanElement; private btnScheduled: HTMLButtonElement; private btnPreloader: HTMLButtonElement; @@ -212,7 +214,7 @@ export default class ChatInput { this.inputContainer.append(this.rowsWrapper, fakeRowsWrapper, fakeSelectionWrapper); this.chatInput.append(this.inputContainer); - this.goDownBtn = ButtonCorner({icon: 'arrow_down', className: 'bubbles-go-down hide'}); + this.goDownBtn = ButtonCorner({icon: 'arrow_down', className: 'bubbles-corner-button bubbles-go-down hide'}); this.inputContainer.append(this.goDownBtn); attachClickEvent(this.goDownBtn, (e) => { @@ -309,6 +311,17 @@ export default class ChatInput { this.goDownUnreadBadge.classList.add('badge', 'badge-24', 'badge-primary'); this.goDownBtn.append(this.goDownUnreadBadge); + this.goMentionBtn = ButtonCorner({icon: 'mention', className: 'bubbles-corner-button bubbles-go-mention'}); + this.goMentionUnreadBadge = document.createElement('span'); + this.goMentionUnreadBadge.classList.add('badge', 'badge-24', 'badge-primary'); + this.goMentionBtn.append(this.goMentionUnreadBadge); + this.inputContainer.append(this.goMentionBtn); + + attachClickEvent(this.goMentionBtn, (e) => { + cancelEvent(e); + this.appMessagesManager.goToNextMention(this.chat.peerId); + }, {listenerSetter: this.listenerSetter}); + this.btnScheduled = ButtonIcon('scheduled btn-scheduled float hide', {noRipple: true}); attachClickEvent(this.btnScheduled, (e) => { @@ -676,6 +689,12 @@ export default class ChatInput { const count = dialog?.unread_count; this.goDownUnreadBadge.innerText = '' + (count || ''); this.goDownUnreadBadge.classList.toggle('badge-gray', this.appNotificationsManager.isPeerLocalMuted(this.chat.peerId, true)); + + if(this.goMentionUnreadBadge && this.chat.type === 'chat') { + const hasMentions = !!dialog?.unread_mentions_count; + this.goMentionUnreadBadge.innerText = hasMentions ? '' + (dialog.unread_mentions_count) : ''; + this.goMentionBtn.classList.toggle('is-visible', hasMentions); + } } public saveDraft() { diff --git a/src/components/singleTransition.ts b/src/components/singleTransition.ts index 6ef6b355..6095f27a 100644 --- a/src/components/singleTransition.ts +++ b/src/components/singleTransition.ts @@ -6,12 +6,28 @@ import rootScope from "../lib/rootScope"; -const SetTransition = (element: HTMLElement, className: string, forwards: boolean, duration: number, onTransitionEnd?: () => void) => { - const timeout = element.dataset.timeout; +const SetTransition = (element: HTMLElement, className: string, forwards: boolean, duration: number, onTransitionEnd?: () => void, useRafs?: number) => { + const {timeout, raf} = element.dataset; if(timeout !== undefined) { clearTimeout(+timeout); } + if(raf !== undefined) { + window.cancelAnimationFrame(+raf); + if(!useRafs) { + delete element.dataset.raf; + } + } + + if(useRafs) { + element.dataset.raf = '' + window.requestAnimationFrame(() => { + delete element.dataset.raf; + SetTransition(element, className, forwards, duration, onTransitionEnd, useRafs - 1); + }); + + return; + } + if(forwards && className) { element.classList.add(className); } diff --git a/src/config/app.ts b/src/config/app.ts index dd04ef0e..c2b084e9 100644 --- a/src/config/app.ts +++ b/src/config/app.ts @@ -16,7 +16,7 @@ export const MAIN_DOMAIN = 'web.telegram.org'; const App = { id: 1025907, hash: '452b0359b988148995f22ff0f4229750', - version: '0.7.2', + version: '0.8.0', langPackVersion: '0.3.3', langPack: 'macos', langPackCode: 'en', diff --git a/src/helpers/slicedArray.ts b/src/helpers/slicedArray.ts index 668c1851..e3ece7b2 100644 --- a/src/helpers/slicedArray.ts +++ b/src/helpers/slicedArray.ts @@ -35,7 +35,6 @@ export interface SliceConstructor { new(...items: ItemType[]): Slice; } -// TODO: Clear empty arrays after deleting items export default class SlicedArray { private slices: Slice[]/* = [[7,6,5],[4,3,2],[1,0,-1]] */; private sliceConstructor: SliceConstructor; diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index 5c3a337f..8b6e5b78 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -43,11 +43,11 @@ import ConnectionStatusComponent from "../../components/connectionStatus"; import appChatsManager from "./appChatsManager"; import { renderImageFromUrlPromise } from "../../helpers/dom/renderImageFromUrl"; import { fastRafPromise } from "../../helpers/schedulers"; -import appPhotosManager from "./appPhotosManager"; import SortedUserList from "../../components/sortedUserList"; import { isTouchSupported } from "../../helpers/touchSupport"; import handleTabSwipe from "../../helpers/dom/handleTabSwipe"; import windowSize from "../../helpers/windowSize"; +import isInDOM from "../../helpers/dom/isInDOM"; export type DialogDom = { avatarEl: AvatarElement, @@ -56,11 +56,12 @@ export type DialogDom = { titleSpanContainer: HTMLSpanElement, statusSpan: HTMLSpanElement, lastTimeSpan: HTMLSpanElement, - unreadMessagesSpan: HTMLSpanElement, + unreadBadge: HTMLElement, + mentionsBadge?: HTMLElement, lastMessageSpan: HTMLSpanElement, containerEl: HTMLElement, listEl: HTMLLIElement, - muteAnimationTimeout?: number + messageEl: HTMLElement }; //const testScroll = false; @@ -1331,7 +1332,7 @@ export class AppDialogsManager { appMessagesManager.getMessageByPeer(dialog.peerId, dialog.top_message); if(!lastMessage.deleted && lastMessage.pFlags.out && lastMessage.peerId !== rootScope.myId/* && dialog.read_outbox_max_id */) { // maybe comment, 06.20.2020 - const isUnread = (lastMessage.pFlags && lastMessage.pFlags.unread) + const isUnread = !!lastMessage.pFlags?.unread /* && dialog.read_outbox_max_id !== 0 */; // maybe uncomment, 31.01.2020 if(isUnread) { @@ -1351,20 +1352,61 @@ export class AppDialogsManager { isPinned = !!dialog.pFlags.pinned; } + const hasUnreadBadge = isPinned || !!dialog.unread_count || dialog.pFlags.unread_mark; + // dom.messageEl.classList.toggle('has-badge', hasBadge); + + const isUnreadBadgeMounted = isInDOM(dom.unreadBadge); + if(hasUnreadBadge && !isUnreadBadgeMounted) { + dom.messageEl.append(dom.unreadBadge); + } + + const hasMentionsBadge = dialog.unread_mentions_count > 1; + const isMentionBadgeMounted = dom.mentionsBadge && isInDOM(dom.mentionsBadge); + if(hasMentionsBadge) { + if(!dom.mentionsBadge) { + dom.mentionsBadge = document.createElement('div'); + dom.mentionsBadge.className = 'dialog-subtitle-badge badge badge-24 mention mention-badge'; + dom.mentionsBadge.innerText = '@'; + dom.messageEl.insertBefore(dom.mentionsBadge, dom.lastMessageSpan.nextSibling); + } + } + + SetTransition(dom.unreadBadge, 'is-visible', hasUnreadBadge, 200, hasUnreadBadge ? undefined : () => { + dom.unreadBadge.remove(); + }, !isUnreadBadgeMounted ? 2 : 0); + + if(dom.mentionsBadge) { + SetTransition(dom.mentionsBadge, 'is-visible', hasMentionsBadge, 200, hasMentionsBadge ? undefined : () => { + dom.mentionsBadge.remove(); + delete dom.mentionsBadge; + }, !isMentionBadgeMounted ? 2 : 0); + } + + if(!hasUnreadBadge) { + return; + } + if(isPinned) { - dom.unreadMessagesSpan.classList.add('tgico-chatspinned', 'tgico'); + dom.unreadBadge.classList.add('tgico-chatspinned', 'tgico'); } else { - dom.unreadMessagesSpan.classList.remove('tgico-chatspinned', 'tgico'); + dom.unreadBadge.classList.remove('tgico-chatspinned', 'tgico'); } - if(dialog.unread_count || dialog.pFlags.unread_mark) { + let isUnread = true, isMention = false; + if(dialog.unread_mentions_count && dialog.unread_count === 1) { + dom.unreadBadge.innerText = '@'; + isMention = true; + // dom.unreadBadge.classList.add('tgico-mention', 'tgico'); + } else if(dialog.unread_count || dialog.pFlags.unread_mark) { //dom.unreadMessagesSpan.innerText = '' + (dialog.unread_count ? formatNumber(dialog.unread_count, 1) : ' '); - dom.unreadMessagesSpan.innerText = '' + (dialog.unread_count || ' '); - dom.unreadMessagesSpan.classList.add('unread'); + dom.unreadBadge.innerText = '' + (dialog.unread_count || ' '); } else { - dom.unreadMessagesSpan.innerText = ''; - dom.unreadMessagesSpan.classList.remove('unread'); + dom.unreadBadge.innerText = ''; + isUnread = false; } + + dom.unreadBadge.classList.toggle('unread', isUnread); + dom.unreadBadge.classList.toggle('mention', isMention); } private accumulateArchivedUnread() { @@ -1499,8 +1541,8 @@ export class AppDialogsManager { const lastTimeSpan = document.createElement('span'); lastTimeSpan.classList.add('message-time'); - const unreadMessagesSpan = document.createElement('div'); - unreadMessagesSpan.className = 'dialog-subtitle-badge badge badge-24'; + const unreadBadge = document.createElement('div'); + unreadBadge.className = 'dialog-subtitle-badge badge badge-24'; const titleP = document.createElement('p'); titleP.classList.add('dialog-title'); @@ -1510,11 +1552,11 @@ export class AppDialogsManager { rightSpan.append(statusSpan, lastTimeSpan); titleP.append(titleSpanContainer, rightSpan); - const messageP = document.createElement('p'); - messageP.classList.add('dialog-subtitle'); - messageP.append(span, unreadMessagesSpan); + const messageEl = document.createElement('p'); + messageEl.classList.add('dialog-subtitle'); + messageEl.append(span); - captionDiv.append(titleP, messageP); + captionDiv.append(titleP, messageEl); const dom: DialogDom = { avatarEl, @@ -1523,10 +1565,11 @@ export class AppDialogsManager { titleSpanContainer, statusSpan, lastTimeSpan, - unreadMessagesSpan, + unreadBadge, lastMessageSpan: span, containerEl: li, - listEl: li + listEl: li, + messageEl }; /* let good = false; diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index adb3a404..6a832968 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -65,6 +65,7 @@ import type { MediaSize } from "../../helpers/mediaSizes"; // TODO: если удалить диалог находясь в папке, то он не удалится из папки и будет виден в настройках const APITIMEOUT = 0; +const DO_NOT_READ_HISTORY = false; export type HistoryStorage = { count: number | null, @@ -205,6 +206,9 @@ export class AppMessagesManager { private middleware: ReturnType; + private unreadMentions: {[peerId: string]: SlicedArray} = {}; + private goToNextMentionPromises: {[peerId: string]: Promise} = {}; + constructor() { this.clear(); @@ -3632,7 +3636,10 @@ export class AppMessagesManager { // TODO: cancel notification by peer when this function is being called public readHistory(peerId: number, maxId = 0, threadId?: number, force = false) { - // return Promise.resolve(); + if(DO_NOT_READ_HISTORY) { + return Promise.resolve(); + } + // console.trace('start read') this.log('readHistory:', peerId, maxId, threadId); if(!this.getReadMaxIdIfUnread(peerId, threadId) && !force) { @@ -3732,32 +3739,127 @@ export class AppMessagesManager { } } + public modifyCachedMentions(peerId: number, mid: number, add: boolean) { + const slicedArray = this.unreadMentions[peerId]; + if(!slicedArray) return; + + if(add) { + if(slicedArray.first.isEnd(SliceEnd.Top)) { + slicedArray.insertSlice([mid]); + } + } else { + slicedArray.delete(mid); + } + } + + public goToNextMention(peerId: number) { + /* this.getUnreadMentions(peerId, 1, 2, 0).then(messages => { + console.log(messages); + }); */ + + const promise = this.goToNextMentionPromises[peerId]; + if(promise) { + return promise; + } + + const slicedArray = this.unreadMentions[peerId] ?? (this.unreadMentions[peerId] = new SlicedArray()); + const length = slicedArray.length; + const isTopEnd = slicedArray.first.isEnd(SliceEnd.Top); + if(!length && isTopEnd) { + return Promise.resolve(); + } + + let loadNextPromise = Promise.resolve(); + if(!isTopEnd && length < 25) { + loadNextPromise = this.loadNextMentions(peerId); + } + + return this.goToNextMentionPromises[peerId] = loadNextPromise.then(() => { + const last = slicedArray.last; + const mid = last && last[last.length - 1]; + if(mid) { + slicedArray.delete(mid); + rootScope.dispatchEvent('history_focus', {peerId, mid}); + } + }).finally(() => { + delete this.goToNextMentionPromises[peerId]; + }); + } + + public loadNextMentions(peerId: number) { + const slicedArray = this.unreadMentions[peerId]; + const maxId = slicedArray.first[0] || 1; + + const backLimit = 50; + const add_offset = -backLimit; + const limit = backLimit; + return this.getUnreadMentions(peerId, maxId, add_offset, limit).then(messages => { + this.mergeHistoryResult(slicedArray, messages, maxId === 1 ? 0 : maxId, limit, add_offset); + }); + } + + public getUnreadMentions(peerId: number, offsetId: number, add_offset: number, limit: number, maxId = 0, minId = 0) { + return apiManager.invokeApiSingle('messages.getUnreadMentions', { + peer: appPeersManager.getInputPeerById(peerId), + offset_id: appMessagesIdsManager.getServerMessageId(offsetId), + add_offset, + limit, + max_id: appMessagesIdsManager.getServerMessageId(maxId), + min_id: appMessagesIdsManager.getServerMessageId(minId) + }).then(messagesMessages => { + assumeType>(messagesMessages); + appUsersManager.saveApiUsers(messagesMessages.users); + appChatsManager.saveApiChats(messagesMessages.chats); + this.saveMessages(messagesMessages.messages); + + return messagesMessages; + }); + } + public readMessages(peerId: number, msgIds: number[]) { + if(DO_NOT_READ_HISTORY) { + return Promise.resolve(); + } + + if(!msgIds.length) { + return Promise.resolve(); + } + msgIds = msgIds.map(mid => appMessagesIdsManager.getServerMessageId(mid)); + let promise: Promise, update: Update.updateChannelReadMessagesContents | Update.updateReadMessagesContents; if(peerId < 0 && appPeersManager.isChannel(peerId)) { const channelId = -peerId; - apiManager.invokeApi('channels.readMessageContents', { + + update = { + _: 'updateChannelReadMessagesContents', + channel_id: channelId, + messages: msgIds + }; + + promise = apiManager.invokeApi('channels.readMessageContents', { channel: appChatsManager.getChannelInput(channelId), id: msgIds - }).then(() => { - apiUpdatesManager.processLocalUpdate({ - _: 'updateChannelReadMessagesContents', - channel_id: channelId, - messages: msgIds - }); }); } else { - apiManager.invokeApi('messages.readMessageContents', { + update = { + _: 'updateReadMessagesContents', + messages: msgIds, + pts: undefined, + pts_count: undefined + }; + + promise = apiManager.invokeApi('messages.readMessageContents', { id: msgIds }).then((affectedMessages) => { - apiUpdatesManager.processLocalUpdate({ - _: 'updateReadMessagesContents', - messages: msgIds, - pts: affectedMessages.pts, - pts_count: affectedMessages.pts_count - }); + (update as Update.updateReadMessagesContents).pts = affectedMessages.pts; + (update as Update.updateReadMessagesContents).pts_count = affectedMessages.pts_count; + apiUpdatesManager.processLocalUpdate(update); }); } + + apiUpdatesManager.processLocalUpdate(update); + + return promise; } public getHistoryStorage(peerId: number, threadId?: number) { @@ -3973,6 +4075,7 @@ export class AppMessagesManager { if(message.pFlags.mentioned) { ++dialog.unread_mentions_count; + this.modifyCachedMentions(peerId, message.mid, true); } } @@ -4135,6 +4238,7 @@ export class AppMessagesManager { if(message.pFlags.mentioned) { newUnreadMentionsCount = --foundDialog.unread_mentions_count; + this.modifyCachedMentions(peerId, message.mid, false); } } @@ -4157,6 +4261,10 @@ export class AppMessagesManager { } else if(newUnreadCount && foundDialog.top_message > maxId) { foundDialog.unread_count = newUnreadCount; } + + if(newUnreadMentionsCount < 0) { + foundDialog.unread_mentions_count = 0; + } } rootScope.dispatchEvent('dialog_unread', {peerId}); @@ -4182,11 +4290,16 @@ export class AppMessagesManager { const channelId = (update as Update.updateChannelReadMessagesContents).channel_id; const mids = (update as Update.updateReadMessagesContents).messages.map(id => appMessagesIdsManager.generateMessageId(id)); const peerId = channelId ? -channelId : this.getMessageById(mids[0]).peerId; - for(const mid of mids) { + for(let i = 0, length = mids.length; i < length; ++i) { + const mid = mids[i]; const message: MyMessage = this.getMessageByPeer(peerId, mid); - if(!message.deleted) { + if(!message.deleted && message.pFlags.media_unread) { delete message.pFlags.media_unread; this.setDialogToStateIfMessageIsTop(message); + + if(!message.pFlags.out && message.pFlags.mentioned) { + this.modifyCachedMentions(peerId, mid, false); + } } } @@ -4259,7 +4372,7 @@ export class AppMessagesManager { const foundDialog = this.getDialogOnly(peerId); if(foundDialog) { if(historyUpdated.unreadMentions) { - foundDialog.unread_mentions_count -= historyUpdated.unreadMentions; + foundDialog.unread_mentions_count = Math.max(0, foundDialog.unread_mentions_count - historyUpdated.unreadMentions); } if(historyUpdated.unread) { @@ -4911,17 +5024,62 @@ export class AppMessagesManager { }); } - public fillHistoryStorage(peerId: number, offset_id: number, limit: number, add_offset: number, historyStorage: HistoryStorage, threadId?: number): Promise { - return this.requestHistory(peerId, offset_id, limit, add_offset, undefined, threadId).then((historyResult) => { - const {offset_id_offset, count, messages} = historyResult as MessagesMessages.messagesMessagesSlice; + public isHistoryResultEnd(historyResult: Exclude, limit: number, add_offset: number) { + const {offset_id_offset, messages} = historyResult as MessagesMessages.messagesMessagesSlice; + + const count = (historyResult as MessagesMessages.messagesMessagesSlice).count || messages.length; + const offsetIdOffset = offset_id_offset || 0; + + const topWasMeantToLoad = add_offset < 0 ? limit + add_offset : limit; + + const isTopEnd = offsetIdOffset >= (count - topWasMeantToLoad) || count < topWasMeantToLoad; + const isBottomEnd = !offsetIdOffset || (add_offset < 0 && (offsetIdOffset + add_offset) <= 0); + + return {count, offsetIdOffset, isTopEnd, isBottomEnd}; + } + + public mergeHistoryResult(slicedArray: SlicedArray, + historyResult: Parameters[0], + offset_id: number, + limit: number, + add_offset: number) { + const {messages} = historyResult as MessagesMessages.messagesMessagesSlice; + const isEnd = this.isHistoryResultEnd(historyResult, limit, add_offset); + const {count, offsetIdOffset, isTopEnd, isBottomEnd} = isEnd; + const mids = messages.map((message) => { + return (message as MyMessage).mid; + }); + + // * add bound manually. + // * offset_id will be inclusive only if there is 'add_offset' <= -1 (-1 - will only include the 'offset_id') + if(offset_id && !mids.includes(offset_id) && offsetIdOffset < count) { + let i = 0; + for(const length = mids.length; i < length; ++i) { + if(offset_id > mids[i]) { + break; + } + } + + mids.splice(i, 0, offset_id); + } - historyStorage.count = count || messages.length; - const offsetIdOffset = offset_id_offset || 0; + const slice = slicedArray.insertSlice(mids) || slicedArray.slice; + if(isTopEnd) { + slice.setEnd(SliceEnd.Top); + } + + if(isBottomEnd) { + slice.setEnd(SliceEnd.Bottom); + } - const topWasMeantToLoad = add_offset < 0 ? limit + add_offset : limit; + return {slice, mids, messages, ...isEnd}; + } + + public fillHistoryStorage(peerId: number, offset_id: number, limit: number, add_offset: number, historyStorage: HistoryStorage, threadId?: number): Promise { + return this.requestHistory(peerId, offset_id, limit, add_offset, undefined, threadId).then((historyResult) => { + const {count, isBottomEnd, slice, messages} = this.mergeHistoryResult(historyStorage.history, historyResult, offset_id, limit, add_offset); - const isTopEnd = offsetIdOffset >= (historyStorage.count - topWasMeantToLoad) || historyStorage.count < topWasMeantToLoad; - const isBottomEnd = !offsetIdOffset || (add_offset < 0 && (offsetIdOffset + add_offset) <= 0); + historyStorage.count = count; /* if(!maxId && historyResult.messages.length) { maxId = this.incrementMessageId((historyResult.messages[0] as MyMessage).mid, 1); @@ -4929,34 +5087,14 @@ export class AppMessagesManager { const wasTotalCount = historyStorage.history.length; */ - const mids = messages.map((message) => { - if(this.mergeReplyKeyboard(historyStorage, message as MyMessage)) { + for(let i = 0, length = messages.length; i < length; ++i) { + const message = messages[i] as MyMessage; + if(this.mergeReplyKeyboard(historyStorage, message)) { rootScope.dispatchEvent('history_reply_markup', {peerId}); } - - return (message as MyMessage).mid; - }); - - // * add bound manually. - // * offset_id will be inclusive only if there is 'add_offset' <= -1 (-1 - will only include the 'offset_id') - if(offset_id && !mids.includes(offset_id) && offsetIdOffset < historyStorage.count) { - let i = 0; - for(const length = mids.length; i < length; ++i) { - if(offset_id > mids[i]) { - break; - } - } - - mids.splice(i, 0, offset_id); - } - - const slice = historyStorage.history.insertSlice(mids) || historyStorage.history.slice; - if(isTopEnd) { - slice.setEnd(SliceEnd.Top); } - + if(isBottomEnd) { - slice.setEnd(SliceEnd.Bottom); historyStorage.maxId = slice[0]; // ! WARNING } @@ -5220,6 +5358,7 @@ export class AppMessagesManager { if(message.pFlags.mentioned) { ++history.unreadMentions; + this.modifyCachedMentions(peerId, mid, false); } } diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss index 8dbf12b9..44f63e08 100644 --- a/src/scss/partials/_chat.scss +++ b/src/scss/partials/_chat.scss @@ -1091,25 +1091,31 @@ $chat-helper-size: 36px; //mask-image: linear-gradient(0deg, transparent 0, #000 28px); //} - & + .chat-input .bubbles-go-down { - cursor: pointer; - //--translateY: 0; - opacity: 1; - visibility: visible; - - @include animation-level(2) { - transition: opacity var(--layer-transition), visibility 0s 0s !important; + & + .chat-input { + .bubbles-go-down { + cursor: pointer; + //--translateY: 0; + opacity: 1; + visibility: visible; + + @include animation-level(2) { + transition: opacity var(--layer-transition), visibility 0s 0s !important; + } + + /* &.is-broadcast { + --translateY: 79px !important; + } */ } - /* &.is-broadcast { - --translateY: 79px !important; - } */ - } + .bubbles-go-mention { + --translateY: calc(var(--chat-input-size) * -1 + -.625rem); + } + } //} } .search-group.search-group-messages { - padding: 0.25rem 0 .5rem; + padding: .25rem 0 .5rem; } } @@ -1198,6 +1204,30 @@ $chat-helper-size: 36px; } .bubbles-go-down { + cursor: default; + opacity: 0; + visibility: hidden; + transform: none !important; + transition: opacity var(--layer-transition), visibility 0s .2s !important; +} + +.bubbles-go-mention { + --translateY: 0; + cursor: default; + opacity: 0; + visibility: hidden; + + &.is-visible { + cursor: pointer; + opacity: 1; + visibility: visible; + transition: transform var(--layer-transition), opacity var(--layer-transition), visibility 0s 0s !important; + } + + transition: transform var(--layer-transition), opacity var(--layer-transition), visibility 0s .2s !important; +} + +.bubbles-corner-button { position: absolute; background-color: var(--surface-color); border-radius: 50%; @@ -1209,17 +1239,11 @@ $chat-helper-size: 36px; right: var(--chat-input-padding); //top: calc((var(--chat-input-size) * -1) - 6px); bottom: calc(var(--chat-input-size) + var(--bottom) + 10px); - cursor: default; - opacity: 0; - visibility: hidden; z-index: 2; //transition: transform var(--layer-transition), opacity var(--layer-transition) !important; overflow: visible; //--translateY: calc(var(--chat-input-size) + 10px); //--translateY: calc(100% + 10px); - transition: opacity var(--layer-transition), visibility 0s .2s !important; - //transition: opacity var(--layer-transition); - transform: none !important; @include animation-level(0) { transition: none !important; diff --git a/src/scss/partials/_chatlist.scss b/src/scss/partials/_chatlist.scss index eaf079a1..9d08f4e3 100644 --- a/src/scss/partials/_chatlist.scss +++ b/src/scss/partials/_chatlist.scss @@ -160,7 +160,6 @@ ul.chatlist { p { margin: 0; display: flex; - justify-content: space-between; flex-direction: row; align-items: flex-start; height: 27px; @@ -222,7 +221,8 @@ ul.chatlist { border-color: var(--primary-color); } - .badge.unread { + .badge.unread, + .mention { background-color: #fff !important; color: var(--primary-color); } @@ -268,13 +268,32 @@ ul.chatlist { margin-top: -3px; &-badge { - &:not(:empty), &.tgico-pinnedchat { - margin-left: .5rem; - } + display: block !important; + margin-top: 4px; + margin-right: -3px; + margin-left: .5rem; + flex: 0 0 auto; - &.unread { + @include animation-level(2) { transition: none; + transform: scale(0); + + &.is-visible:not(.backwards) { + transform: scale(1); + } + + &.animating { + transition: background-color .2s ease-in-out, transform .2s ease-in-out; + } } + + /* &:not(:empty), &.tgico-pinnedchat { + margin-left: .5rem; + } */ + + /* &.unread { + transition: none; + } */ } } } @@ -292,7 +311,8 @@ ul.chatlist { padding: .0625rem .4375rem .0625rem .5625rem; } - .dialog-avatar, .user-caption { + .dialog-avatar, + .user-caption { pointer-events: none; position: relative; // for z-index } @@ -337,7 +357,10 @@ ul.chatlist { } */ } - .user-title, .user-last-message { + .user-title, + .user-last-message { + flex-grow: 1; + i { font-style: normal; //color: var(--primary-color); @@ -365,24 +388,18 @@ ul.chatlist { vertical-align: middle; } - .dialog-subtitle-badge { - margin-top: 4px; - margin-right: -3px; - margin-left: .25rem; - flex: 0 0 auto; - } - .tgico-chatspinned { background: transparent; - position: relative; + + @include animation-level(2) { + &:before { + transition: opacity .2s ease-in-out; + } + } &:before { color: var(--chatlist-pinned-color); - transition: opacity .2s ease-in-out; opacity: 1; - position: absolute; - top: 0; - left: 0; } &.unread { @@ -392,11 +409,39 @@ ul.chatlist { } } - .unread, li.is-muted.backwards .unread { + .tgico-chatspinned/* , + .tgico-mention */ { + position: relative; + + &:before { + position: absolute; + top: 0; + left: 0; + } + } + + .mention { + padding: 0; + background-color: var(--chatlist-status-color) !important; + } + + .mention-badge { + margin-right: -2px; + } + + /* .tgico-mention { + &:before { + color: #fff !important; + opacity: 1 !important; + } + } */ + + .unread, + .is-muted.backwards .unread { background-color: var(--chatlist-status-color); } - li.is-muted .unread { + .is-muted .unread { background-color: var(--secondary-color); } diff --git a/src/scss/partials/_leftSidebar.scss b/src/scss/partials/_leftSidebar.scss index f3cf42f3..94bb5551 100644 --- a/src/scss/partials/_leftSidebar.scss +++ b/src/scss/partials/_leftSidebar.scss @@ -162,6 +162,7 @@ top: 6px; z-index: 1; margin-left: 0; + line-height: 1.875rem; } &.is-picked { diff --git a/src/scss/partials/_mediaViewer.scss b/src/scss/partials/_mediaViewer.scss index 5d80e6a0..60e00dd5 100644 --- a/src/scss/partials/_mediaViewer.scss +++ b/src/scss/partials/_mediaViewer.scss @@ -122,7 +122,8 @@ $inactive-opacity: .4; overflow: hidden; text-overflow: ellipsis; z-index: 4; - bottom: .75rem; + // bottom: .75rem; + bottom: 0; left: 0; right: 0; padding: .5rem .5rem 0; diff --git a/src/scss/partials/_selector.scss b/src/scss/partials/_selector.scss index 1bbe938a..cfa86cfe 100644 --- a/src/scss/partials/_selector.scss +++ b/src/scss/partials/_selector.scss @@ -54,14 +54,14 @@ &-user { color: var(--primary-text-color); background-color: var(--light-secondary-text-color); - font-size: 16px; + font-size: 1rem; padding: 0 17px 0px 0px; - line-height: 1.875rem; - margin-left: -4px; - margin-right: 12px; - height: 32px; + margin-left: -.25rem; + margin-right: .75rem; + height: 2rem; + line-height: 2rem; margin-bottom: 7px; - border-radius: 24px; + border-radius: 1.5rem; user-select: none; flex: 0 0 auto; transition: .2s opacity, .2s transform, .2s background-color;