From f966e7610f180d92967b09c895ae9518606bbcad Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Sun, 20 Dec 2020 05:54:35 +0200 Subject: [PATCH] Comments alpha version --- src/components/audio.ts | 8 +- src/components/chat/bubbles.ts | 165 +++++---- src/components/chat/chat.ts | 19 +- src/components/chat/input.ts | 11 +- src/components/chat/messageRender.ts | 55 +++ src/components/chat/selection.ts | 4 +- src/components/chat/topbar.ts | 18 +- src/components/poll.ts | 2 +- src/components/popups/createPoll.ts | 6 + src/components/popups/datePicker.ts | 52 ++- src/components/popups/deleteMessages.ts | 6 +- src/components/popups/newMedia.ts | 16 +- src/components/popups/schedule.ts | 3 +- src/components/sidebarLeft/tabs/addMembers.ts | 4 +- src/components/wrappers.ts | 2 +- src/lib/appManagers/appImManager.ts | 4 +- src/lib/appManagers/appMessagesManager.ts | 345 ++++++++++-------- src/lib/rootScope.ts | 2 +- src/scss/partials/_avatar.scss | 5 + src/scss/partials/_chatBubble.scss | 84 ++++- src/scss/partials/_fonts.scss | 2 +- src/scss/partials/_ico.scss | 2 +- src/scss/partials/popups/_datePicker.scss | 91 ++++- 23 files changed, 614 insertions(+), 292 deletions(-) diff --git a/src/components/audio.ts b/src/components/audio.ts index ee3a4129..ef140bed 100644 --- a/src/components/audio.ts +++ b/src/components/audio.ts @@ -66,7 +66,7 @@ function wrapVoiceMessage(audioEl: AudioElement) { audioEl.classList.add('is-voice'); const message = audioEl.message; - const doc = message.media.document as MyDocument; + const doc = (message.media.document || message.media.webpage.document) as MyDocument; const isOut = message.fromId == rootScope.myId && message.peerId != rootScope.myId; let isUnread = message && message.pFlags.media_unread; if(isUnread) { @@ -254,7 +254,7 @@ function wrapVoiceMessage(audioEl: AudioElement) { function wrapAudio(audioEl: AudioElement) { const withTime = audioEl.withTime; - const doc = audioEl.message.media.document; + const doc = audioEl.message.media.document || audioEl.message.media.webpage.document; const title = doc.audioTitle || doc.file_name; let subtitle = doc.audioPerformer ? RichTextProcessor.wrapPlainText(doc.audioPerformer) : ''; @@ -334,7 +334,7 @@ export default class AudioElement extends HTMLElement { this.classList.add('audio'); - const doc = this.message.media.document; + const doc = this.message.media.document || this.message.media.webpage.document; const uploading = this.message.pFlags.is_outgoing; const durationStr = String(doc.duration | 0).toHHMMSS(true); @@ -360,7 +360,7 @@ export default class AudioElement extends HTMLElement { audioTimeDiv.innerHTML = durationStr; const onLoad = (autoload = true) => { - const audio = this.audio = appMediaPlaybackController.addMedia(this.message.peerId, this.message.media.document, this.message.mid, autoload); + const audio = this.audio = appMediaPlaybackController.addMedia(this.message.peerId, this.message.media.document || this.message.media.webpage.document, this.message.mid, autoload); this.onTypeDisconnect = onTypeLoad(); diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index 8a24e79f..56bf42ae 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -37,6 +37,7 @@ import Chat from "./chat"; import ListenerSetter from "../../helpers/listenerSetter"; import PollElement from "../poll"; import AudioElement from "../audio"; +import { MessageReplies, MessageReplyHeader } from "../../layer"; const IGNORE_ACTIONS = ['messageActionHistoryClear']; @@ -104,7 +105,7 @@ export default class ChatBubbles { public replyFollowHistory: number[] = []; constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appStickersManager: AppStickersManager, private appUsersManager: AppUsersManager, private appInlineBotsManager: AppInlineBotsManager, private appPhotosManager: AppPhotosManager, private appDocsManager: AppDocsManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager) { - this.chat.log.error('Bubbles construction'); + //this.chat.log.error('Bubbles construction'); this.listenerSetter = new ListenerSetter(); @@ -358,10 +359,9 @@ export default class ChatBubbles { this.listenerSetter.add(rootScope, 'dialog_unread', (e) => { const info = e.detail; - const dialog = this.appMessagesManager.getDialogByPeerId(info.peerId)[0]; - if(dialog?.peerId == this.peerId) { + if(info.peerId == this.peerId) { this.chat.input.setUnreadCount(); - this.updateUnreadByDialog(dialog); + this.updateUnreadByDialog(); } }); @@ -510,6 +510,21 @@ export default class ChatBubbles { return; } + const commentsDiv: HTMLElement = findUpClassName(target, 'replies-footer'); + if(commentsDiv) { + const mid = +bubble.dataset.mid; + const message = this.chat.getMessage(mid); + const replies = message.replies as MessageReplies; + if(replies) { + this.appMessagesManager.getDiscussionMessage(this.peerId, mid).then(result => { + const message = result.messages[0]; + this.chat.appImManager.setInnerPeer(-replies.channel_id, (message as MyMessage).mid, 'discussion'); + }); + } + + return; + } + //this.log('chatInner click:', target); const isVideoComponentElement = target.tagName == 'SPAN'; /* if(isVideoComponentElement) { @@ -634,7 +649,12 @@ export default class ChatBubbles { if(isReplyClick && bubble.classList.contains('is-reply')/* || bubble.classList.contains('forwarded') */) { this.replyFollowHistory.push(+bubble.dataset.mid); let originalMessageId = +bubble.getAttribute('data-original-mid'); - this.chat.appImManager.setInnerPeer(this.peerId, originalMessageId); + + 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") { @@ -673,14 +693,15 @@ export default class ChatBubbles { const mid = this.replyFollowHistory.pop(); this.chat.setPeer(this.peerId, mid); } else { - const dialog = this.appMessagesManager.getDialogByPeerId(this.peerId)[0]; + this.chat.setPeer(this.peerId/* , dialog.top_message */); + // const dialog = this.appMessagesManager.getDialogByPeerId(this.peerId)[0]; - if(dialog) { - this.chat.setPeer(this.peerId/* , dialog.top_message */); - } else { - this.log('will scroll down 3'); - this.scroll.scrollTop = this.scroll.scrollHeight; - } + // if(dialog) { + // this.chat.setPeer(this.peerId/* , dialog.top_message */); + // } else { + // this.log('will scroll down 3'); + // this.scroll.scrollTop = this.scroll.scrollHeight; + // } } } @@ -745,10 +766,11 @@ export default class ChatBubbles { if(this.scrolledAllDown) return; - let dialog = this.appMessagesManager.getDialogByPeerId(this.peerId)[0]; + //let dialog = this.appMessagesManager.getDialogByPeerId(this.peerId)[0]; + const historyStorage = this.appMessagesManager.getHistoryStorage(this.peerId, this.chat.threadId); // if scroll down after search - if(!top && (!dialog || history.indexOf(dialog.top_message) === -1)/* && this.chat.type == 'chat' */) { + if(!top && history.indexOf(historyStorage.maxId) === -1/* && this.chat.type == 'chat' */) { this.log('Will load more (down) history by maxId:', history[history.length - 1], history); /* false && */this.getHistory(history[history.length - 1], false, true, undefined, justLoad); } @@ -833,14 +855,15 @@ export default class ChatBubbles { } } - public updateUnreadByDialog(dialog: Dialog) { - let maxId = this.peerId == rootScope.myId ? dialog.read_inbox_max_id : dialog.read_outbox_max_id; + public updateUnreadByDialog() { + const historyStorage = this.appMessagesManager.getHistoryStorage(this.peerId, this.chat.threadId); + const maxId = this.peerId == rootScope.myId ? historyStorage.readMaxId : historyStorage.readOutboxMaxId; ///////this.log('updateUnreadByDialog', maxId, dialog, this.unreadOut); - for(let msgId of this.unreadOut) { + for(const msgId of this.unreadOut) { if(msgId > 0 && msgId <= maxId) { - let bubble = this.bubbles[msgId]; + const bubble = this.bubbles[msgId]; if(bubble) { bubble.classList.remove('is-sent'); bubble.classList.add('is-read'); @@ -884,6 +907,14 @@ export default class ChatBubbles { return; } + if(this.chat.threadId) { + mids = mids.filter(mid => { + const message = this.chat.getMessage(mid); + const replyTo = message.reply_to as MessageReplyHeader; + return replyTo && (replyTo.reply_to_top_id || replyTo.reply_to_msg_id) === this.chat.threadId; + }); + } + mids = mids.filter(mid => !this.bubbles[mid]); mids.forEach((mid: number) => { const message = this.chat.getMessage(mid); @@ -993,7 +1024,7 @@ export default class ChatBubbles { } public destroy() { - this.chat.log.error('Bubbles destroying'); + //this.chat.log.error('Bubbles destroying'); this.scrollable.onScrolledTop = this.scrollable.onScrolledBottom = this.scrollable.onAdditionalScroll = null; @@ -1068,15 +1099,16 @@ export default class ChatBubbles { const samePeer = this.peerId == peerId; - const dialog = this.appMessagesManager.getDialogByPeerId(peerId)[0] || null; - let topMessage = lastMsgId <= 0 ? lastMsgId : dialog?.top_message ?? 0; + const historyStorage = this.appMessagesManager.getHistoryStorage(peerId, this.chat.threadId); + let topMessage = lastMsgId <= 0 ? lastMsgId : historyStorage.maxId ?? 0; const isTarget = lastMsgId !== undefined; - if(!isTarget && dialog) { - if(dialog.unread_count && !samePeer) { - lastMsgId = dialog.read_inbox_max_id; + if(!isTarget && historyStorage.maxId) { + const isUnread = this.appMessagesManager.isHistoryUnread(peerId, this.chat.threadId); + if(/* dialog.unread_count */isUnread && !samePeer) { + lastMsgId = historyStorage.readMaxId; } else { - lastMsgId = dialog.top_message; + lastMsgId = historyStorage.maxId; //lastMsgID = topMessage; } } @@ -1090,7 +1122,7 @@ export default class ChatBubbles { this.scrollable.scrollIntoView(mounted.bubble); this.highlightBubble(mounted.bubble); this.chat.setListenerResult('setPeer', lastMsgId, false); - } else if(dialog && !isJump) { + } else if(historyStorage.maxId && !isJump) { //this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight); this.scroll.scrollTop = this.scroll.scrollHeight; this.chat.setListenerResult('setPeer', lastMsgId, true); @@ -1108,7 +1140,7 @@ export default class ChatBubbles { this.replyFollowHistory.length = 0; } - this.log('setPeer peerId:', this.peerId, dialog, lastMsgId, topMessage); + this.log('setPeer peerId:', this.peerId, historyStorage, lastMsgId, topMessage); // add last message, bc in getHistory will load < max_id const additionMsgId = isJump || this.chat.type !== 'chat' ? 0 : topMessage; @@ -1187,14 +1219,14 @@ export default class ChatBubbles { this.lazyLoadQueue.unlock(); //if(dialog && lastMsgID && lastMsgID != topMessage && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) { - if(dialog && (isTarget || isJump)) { + if(historyStorage.maxId && (isTarget || isJump)) { if(this.scrollable.scrollLocked) { clearTimeout(this.scrollable.scrollLocked); this.scrollable.scrollLocked = 0; } const fromUp = maxBubbleId > 0 && (maxBubbleId < lastMsgId || lastMsgId < 0); - const forwardingUnread = dialog.read_inbox_max_id == lastMsgId && !isTarget; + const forwardingUnread = historyStorage.readMaxId === lastMsgId && !isTarget; if(!fromUp && (samePeer || forwardingUnread)) { this.scrollable.scrollTop = this.scrollable.scrollHeight; } else if(fromUp/* && (samePeer || forwardingUnread) */) { @@ -1228,12 +1260,15 @@ export default class ChatBubbles { this.log('scrolledAllDown:', this.scrolledAllDown); //if(!this.unreaded.length && dialog) { // lol - if(this.scrolledAllDown && dialog) { // lol - this.appMessagesManager.readHistory(peerId, dialog.top_message); + if(this.scrolledAllDown && historyStorage.maxId) { // lol + this.appMessagesManager.readHistory(peerId, historyStorage.maxId); } - if(dialog?.pFlags?.unread_mark) { - this.appMessagesManager.markDialogUnread(peerId, true); + if(this.chat.type === 'chat') { + const dialog = this.appMessagesManager.getDialogByPeerId(peerId)[0]; + if(dialog?.pFlags.unread_mark) { + this.appMessagesManager.markDialogUnread(peerId, true); + } } this.chatInner.classList.remove('disable-hover', 'is-scrolling'); // warning, performance! @@ -1653,6 +1688,8 @@ export default class ChatBubbles { bubble.classList.add(status); } + const withReplyFooter = message.replies && message.replies.pFlags.comments; + const isOut = our && (!message.fwd_from || this.peerId != rootScope.myId); let nameContainer = bubbleContainer; @@ -1693,7 +1730,7 @@ export default class ChatBubbles { const photo = this.appPhotosManager.getPhoto(message.id); //if(photo._ == 'photoEmpty') break; this.log('will wrap pending photo:', pending, message, photo); - const withTail = !isAndroid && !message.message; + const withTail = !isAndroid && !message.message && !withReplyFooter; if(withTail) bubble.classList.add('with-media-tail'); wrapPhoto({ photo, message, @@ -1714,7 +1751,7 @@ export default class ChatBubbles { let doc = this.appDocsManager.getDoc(message.id); //if(doc._ == 'documentEmpty') break; this.log('will wrap pending video:', pending, message, doc); - const withTail = !isAndroid && !isApple && doc.type != 'round' && !message.message; + const withTail = !isAndroid && !isApple && doc.type != 'round' && !message.message && !withReplyFooter; if(withTail) bubble.classList.add('with-media-tail'); wrapVideo({ doc, @@ -1789,7 +1826,7 @@ export default class ChatBubbles { break; } - const withTail = !isAndroid && !message.message; + const withTail = !isAndroid && !message.message && !withReplyFooter; if(withTail) bubble.classList.add('with-media-tail'); wrapPhoto({ photo, @@ -1917,7 +1954,7 @@ export default class ChatBubbles { box.append(quote); //bubble.prepend(box); - bubbleContainer.prepend(timeSpan, box); + messageDiv.insertBefore(box, messageDiv.lastElementChild); //this.log('night running', bubble.scrollHeight); @@ -1973,7 +2010,7 @@ export default class ChatBubbles { chat: this.chat }); } else { - const withTail = !isAndroid && !isApple && doc.type != 'round' && !message.message; + const withTail = !isAndroid && !isApple && doc.type != 'round' && !message.message && withReplyFooter; if(withTail) bubble.classList.add('with-media-tail'); wrapVideo({ doc, @@ -2123,7 +2160,7 @@ export default class ChatBubbles { nameContainer.append(nameDiv); } } else { - if(message.reply_to_mid) { + 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) || ''; @@ -2186,7 +2223,7 @@ export default class ChatBubbles { if(savedFrom) { const goto = document.createElement('div'); - goto.classList.add('bubble-beside-button', 'goto-original', 'tgico-next'); + goto.classList.add('bubble-beside-button', 'goto-original', 'tgico-arrow-next'); bubbleContainer.append(goto); bubble.dataset.savedFrom = savedFrom; bubble.classList.add('with-beside-button'); @@ -2201,6 +2238,15 @@ export default class ChatBubbles { this.bubbleGroups.updateGroupByMessageId(message.mid); } + if(withReplyFooter) { + MessageRender.renderReplies({ + bubble, + bubbleContainer, + message, + messageDiv + }); + } + return bubble; } @@ -2229,14 +2275,9 @@ export default class ChatBubbles { } } */ - let dialog = this.appMessagesManager.getDialogByPeerId(this.peerId)[0]; - if(dialog && dialog.top_message) { - for(let mid of history) { - if(mid == dialog.top_message) { - this.scrolledAllDown = true; - break; - } - } + const historyStorage = this.appMessagesManager.getHistoryStorage(this.peerId, this.chat.threadId); + if(history.includes(historyStorage.maxId)) { + this.scrolledAllDown = true; } //console.time('appImManager render history'); @@ -2332,21 +2373,12 @@ export default class ChatBubbles { public requestHistory(maxId: number, loadCount: number, backLimit: number) { //const middleware = this.getMiddleware(); - if(this.chat.type === 'chat') { - return this.appMessagesManager.getHistory(this.peerId, maxId, loadCount, backLimit); + if(this.chat.type === 'chat' || this.chat.type === 'discussion') { + return this.appMessagesManager.getHistory(this.peerId, maxId, loadCount, backLimit, this.chat.threadId); } else if(this.chat.type === 'pinned') { const promise = this.appMessagesManager.getSearch(this.peerId, '', {_: 'inputMessagesFilterPinned'}, maxId, loadCount, 0, backLimit) .then(value => ({history: value.history.map(m => m.mid)})); - /* if(maxId) { - promise.then(result => { - if(!middleware()) return; - - this.messagesCount = result.count; - this.chat.topbar.setTitle(); - }); - } */ - return promise; } else if(this.chat.type === 'scheduled') { return this.appMessagesManager.getScheduledMessages(this.peerId).then(mids => { @@ -2403,7 +2435,7 @@ export default class ChatBubbles { let additionMsgIds: number[]; if(additionMsgId && !isBackLimit) { - const historyStorage = this.appMessagesManager.getHistoryStorage(peerId); + const historyStorage = this.appMessagesManager.getHistoryStorage(peerId, this.chat.threadId); if(historyStorage.history.length < loadCount) { additionMsgIds = historyStorage.history.slice(); @@ -2554,8 +2586,8 @@ export default class ChatBubbles { // preload more //if(!isFirstMessageRender) { - if(this.chat.type === 'chat') { - const storage = this.appMessagesManager.getHistoryStorage(peerId); + if(this.chat.type === 'chat' || this.chat.type === 'discussion') { + const storage = this.appMessagesManager.getHistoryStorage(peerId, this.chat.threadId); const isMaxIdInHistory = storage.history.indexOf(maxId) !== -1; if(isMaxIdInHistory) { // * otherwise it is a search or jump setTimeout(() => { @@ -2578,10 +2610,11 @@ export default class ChatBubbles { return; } - let dialog = this.appMessagesManager.getDialogByPeerId(this.peerId)[0]; - if(!dialog?.unread_count) return; + const historyStorage = this.appMessagesManager.getHistoryStorage(this.peerId, this.chat.threadId); + const isUnread = this.appMessagesManager.isHistoryUnread(this.peerId, this.chat.threadId); + if(!isUnread) return; - let maxId = dialog.read_inbox_max_id; + let maxId = historyStorage.readMaxId; maxId = Object.keys(this.bubbles).filter(mid => !this.bubbles[mid].classList.contains('is-out')).map(i => +i).sort((a, b) => a - b).find(i => i > maxId); if(maxId && this.bubbles[maxId]) { @@ -2591,7 +2624,7 @@ export default class ChatBubbles { this.firstUnreadBubble = null; } - if(maxId != dialog.top_message) { + if(maxId !== historyStorage.maxId) { bubble.classList.add('is-first-unread'); } diff --git a/src/components/chat/chat.ts b/src/components/chat/chat.ts index 54df773e..b8329994 100644 --- a/src/components/chat/chat.ts +++ b/src/components/chat/chat.ts @@ -36,6 +36,7 @@ export default class Chat extends EventListenerBase<{ public contextMenu: ChatContextMenu; public peerId = 0; + public threadId: number; public setPeerPromise: Promise; public peerChanged: boolean; @@ -55,7 +56,7 @@ export default class Chat extends EventListenerBase<{ // * constructor end this.log = logger('CHAT', LogLevels.log | LogLevels.warn | LogLevels.debug | LogLevels.error); - this.log.error('Chat construction'); + //this.log.error('Chat construction'); this.container.append(this.backgroundEl); this.appImManager.chatsContainer.append(this.container); @@ -95,6 +96,9 @@ export default class Chat extends EventListenerBase<{ } else if(this.type === 'scheduled') { this.bubbles.constructScheduledHelpers(); this.input.constructPeerHelpers(); + } else if(this.type === 'discussion') { + this.bubbles.constructPeerHelpers(); + this.input.constructPeerHelpers(); } this.container.classList.add('type-' + this.type); @@ -102,7 +106,7 @@ export default class Chat extends EventListenerBase<{ } public destroy() { - const perf = performance.now(); + //const perf = performance.now(); this.topbar.destroy(); this.bubbles.destroy(); @@ -116,7 +120,7 @@ export default class Chat extends EventListenerBase<{ this.container.remove(); - this.log.error('Chat destroy time:', performance.now() - perf); + //this.log.error('Chat destroy time:', performance.now() - perf); } public cleanup() { @@ -132,6 +136,11 @@ export default class Chat extends EventListenerBase<{ this.init = null; } + if(this.type === 'discussion' && !this.threadId) { + this.threadId = lastMsgId; + lastMsgId = 0; + } + //console.time('appImManager setPeer'); //console.time('appImManager setPeer pre promise'); ////console.time('appImManager: pre render start'); @@ -178,9 +187,9 @@ export default class Chat extends EventListenerBase<{ appSidebarRight.sharedMediaTab.setLoadMutex(this.setPeerPromise); appSidebarRight.sharedMediaTab.loadSidebarMedia(true); - this.setPeerPromise.then(() => { + /* this.setPeerPromise.then(() => { appSidebarRight.sharedMediaTab.loadSidebarMedia(false); - }); + }); */ return this.setPeerPromise; } diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index 0a50eb3b..ee312bd3 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -89,7 +89,7 @@ export default class ChatInput { private scrollOffsetTop = 0; private scrollDiff = 0; - private helperType: Exclude; + public helperType: Exclude; private helperFunc: () => void; private helperWaitingForward: boolean; @@ -382,7 +382,8 @@ export default class ChatInput { duration, waveform: result.waveform, objectURL: result.url, - replyToMsgId: this.replyToMsgId + replyToMsgId: this.replyToMsgId, + threadId: this.chat.threadId }); this.onMessageSent(false, true); @@ -455,7 +456,7 @@ export default class ChatInput { } public destroy() { - this.chat.log.error('Input destroying'); + //this.chat.log.error('Input destroying'); emoticonsDropdown.events.onOpen.findAndSplice(f => f == this.onEmoticonsOpen); emoticonsDropdown.events.onClose.findAndSplice(f => f == this.onEmoticonsClose); @@ -1108,6 +1109,7 @@ export default class ChatInput { } else { this.appMessagesManager.sendText(this.chat.peerId, str, { replyToMsgId: this.replyToMsgId, + threadId: this.chat.threadId, noWebPage: this.noWebPage, webPage: this.willSendWebPage, scheduleDate: this.scheduleDate, @@ -1151,6 +1153,7 @@ export default class ChatInput { this.appMessagesManager.sendFile(this.chat.peerId, document, { isMedia: true, replyToMsgId: this.replyToMsgId, + threadId: this.chat.threadId, silent: this.sendSilent, scheduleDate: this.scheduleDate }); @@ -1235,7 +1238,7 @@ export default class ChatInput { this.willSendWebPage = null; } - this.replyToMsgId = undefined; + this.replyToMsgId = this.chat.threadId; this.forwardingMids.length = 0; this.forwardingFromPeerId = 0; this.editMsgId = undefined; diff --git a/src/components/chat/messageRender.ts b/src/components/chat/messageRender.ts index 8105970d..1ba5ff02 100644 --- a/src/components/chat/messageRender.ts +++ b/src/components/chat/messageRender.ts @@ -1,6 +1,9 @@ import { getFullDate } from "../../helpers/date"; import { formatNumber } from "../../helpers/number"; +import { MessageReplies } from "../../layer"; +import appPeersManager from "../../lib/appManagers/appPeersManager"; import RichTextProcessor from "../../lib/richtextprocessor"; +import { ripple } from "../ripple"; import Chat from "./chat"; type Message = any; @@ -58,4 +61,56 @@ export namespace MessageRender { return timeSpan; }; + + export const renderReplies = ({bubble, bubbleContainer, message, messageDiv}: { + bubble: HTMLElement, + bubbleContainer: HTMLElement, + message: any, + messageDiv: HTMLElement + }) => { + const replies = message.replies as MessageReplies; + const isFooter = !bubble.classList.contains('sticker') && !bubble.classList.contains('emoji-big'); + if(isFooter) { + const container = document.createElement('div'); + container.classList.add('replies-footer'); + + let leftHTML = '', lastStyle = ''; + if(replies?.recent_repliers) { + leftHTML += ''; + } else { + leftHTML = ''; + } + + let text: string; + if(replies?.replies) { + text = replies.replies + ' ' + (replies.replies > 1 ? 'Comments' : 'Comment'); + } else { + text = 'Leave a Comment'; + } + + if(replies) { + if(replies.read_max_id < replies.max_id) { + container.classList.add('is-unread'); + } + } + + container.innerHTML = `${leftHTML}${text}`; + + const rippleContainer = document.createElement('div'); + container.append(rippleContainer); + ripple(rippleContainer); + bubbleContainer.prepend(container); + } + }; } \ No newline at end of file diff --git a/src/components/chat/selection.ts b/src/components/chat/selection.ts index 1b17386d..6148843c 100644 --- a/src/components/chat/selection.ts +++ b/src/components/chat/selection.ts @@ -304,9 +304,7 @@ export default class ChatSelection { this.cancelSelection(); }) }); - } - - if(this.chat.type === 'chat' || this.chat.type === 'pinned') { + } else { this.selectionForwardBtn = Button('btn-primary btn-transparent selection-container-forward', {icon: 'forward'}); this.selectionForwardBtn.append('Forward'); this.listenerSetter.add(this.selectionForwardBtn, 'click', () => { diff --git a/src/components/chat/topbar.ts b/src/components/chat/topbar.ts index 61f55790..0a4c8c52 100644 --- a/src/components/chat/topbar.ts +++ b/src/components/chat/topbar.ts @@ -50,7 +50,7 @@ export default class ChatTopbar { } public construct() { - this.chat.log.error('Topbar construction'); + //this.chat.log.error('Topbar construction'); this.container = document.createElement('div'); this.container.classList.add('sidebar-header', 'topbar'); @@ -320,7 +320,7 @@ export default class ChatTopbar { }; public destroy() { - this.chat.log.error('Topbar destroying'); + //this.chat.log.error('Topbar destroying'); this.listenerSetter.removeAll(); mediaSizes.removeListener('changeScreen', this.onChangeScreen); @@ -386,7 +386,6 @@ export default class ChatTopbar { public setTitle(count?: number) { let title = ''; if(this.chat.type === 'pinned') { - //title = !count ? 'Pinned Messages' : (count === 1 ? 'Pinned Message' : (count + ' Pinned Messages')); title = [count > 1 ? count : false, 'Pinned Messages'].filter(Boolean).join(' '); if(count === undefined) { @@ -408,10 +407,8 @@ export default class ChatTopbar { } } else if(this.chat.type === 'scheduled') { if(this.peerId === rootScope.myId) { - //title = !count ? 'Reminders' : (count === 1 ? 'Reminder' : (count + ' Reminders')); title = [count > 1 ? count : false, 'Reminders'].filter(Boolean).join(' '); } else { - //title = !count ? 'Scheduled Messages' : (count === 1 ? 'Scheduled Message' : (count + ' Scheduled Messages')); title = [count > 1 ? count : false, 'Scheduled Messages'].filter(Boolean).join(' '); } @@ -420,6 +417,17 @@ export default class ChatTopbar { this.setTitle(mids.length); }); } + } else if(this.chat.type === 'discussion') { + title = [count > 1 ? count : false, 'Comments'].filter(Boolean).join(' '); + + if(count === undefined) { + Promise.all([ + this.appMessagesManager.getHistory(this.peerId, 0, 1, 0, this.chat.threadId), + Promise.resolve() + ]).then(() => { + this.setTitle(this.appMessagesManager.getHistoryStorage(this.peerId, this.chat.threadId).count); + }); + } } else if(this.chat.type === 'chat') { if(this.peerId == rootScope.myId) title = 'Saved Messages'; else title = this.appPeersManager.getPeerTitle(this.peerId); diff --git a/src/components/poll.ts b/src/components/poll.ts index cc8f97be..24037a94 100644 --- a/src/components/poll.ts +++ b/src/components/poll.ts @@ -531,7 +531,7 @@ export default class PollElement extends HTMLElement { */ results.recent_voters/* .slice().reverse() */.forEach((userId, idx) => { const style = idx == 0 ? '' : `style="transform: translateX(-${idx * 3}px);"`; - html += ``; + html += ``; }); this.avatarsDiv.innerHTML = html; } diff --git a/src/components/popups/createPoll.ts b/src/components/popups/createPoll.ts index 8f41ab62..cb092b8d 100644 --- a/src/components/popups/createPoll.ts +++ b/src/components/popups/createPoll.ts @@ -241,10 +241,16 @@ export default class PopupCreatePoll extends PopupElement { //console.log('Will try to create poll:', inputMediaPoll); this.chat.appMessagesManager.sendOther(this.chat.peerId, inputMediaPoll, { + threadId: this.chat.threadId, + replyToMsgId: this.chat.input.replyToMsgId, scheduleDate: this.chat.input.scheduleDate, silent: this.chat.input.sendSilent }); + if(this.chat.input.helperType === 'reply') { + this.chat.input.clearHelper(); + } + this.chat.input.onMessageSent(false, false); } diff --git a/src/components/popups/datePicker.ts b/src/components/popups/datePicker.ts index c07bb196..e502ccc0 100644 --- a/src/components/popups/datePicker.ts +++ b/src/components/popups/datePicker.ts @@ -28,7 +28,8 @@ export default class PopupDatePicker extends PopupElement { noTitle: true, minDate: Date, maxDate: Date - withTime: true + withTime: true, + showOverflowMonths: true }> & PopupOptions = {}) { super('popup-date-picker', options.noButtons ? [] : [{ text: 'CANCEL', @@ -242,7 +243,7 @@ export default class PopupDatePicker extends PopupElement { }); } - this.btnConfirm.innerText = 'Send ' + dayStr + ' at ' + ('00' + this.hoursInputField.value).slice(-2) + ':' + ('00' + this.minutesInputField.value).slice(-2); + this.btnConfirm.firstChild.nodeValue = 'Send ' + dayStr + ' at ' + ('00' + this.hoursInputField.value).slice(-2) + ':' + ('00' + this.minutesInputField.value).slice(-2); } } @@ -251,6 +252,21 @@ export default class PopupDatePicker extends PopupElement { this.title.innerText = splitted[0] + ', ' + splitted[1] + ' ' + splitted[2]; } + private renderElement(disabled: boolean, innerText = '') { + const el = document.createElement('button'); + el.classList.add('btn-icon', 'date-picker-month-date'); + + if(disabled) { + el.setAttribute('disabled', 'true'); + } + + if(innerText) { + el.innerText = innerText; + } + + return el; + } + public setMonth() { this.monthTitle.innerText = months[this.selectedMonth.getMonth()] + ' ' + this.selectedMonth.getFullYear(); @@ -263,8 +279,9 @@ export default class PopupDatePicker extends PopupElement { const days = ['M', 'T', 'W', 'T', 'F', 'S', 'S']; this.month.append(...days.map(s => { - const el = document.createElement('span'); - el.innerText = s; + const el = this.renderElement(true, s); + el.classList.remove('date-picker-month-date'); + el.classList.add('date-picker-month-day'); return el; })); @@ -274,23 +291,24 @@ export default class PopupDatePicker extends PopupElement { let dayIndex = firstDate.getDay() - 1; if(dayIndex == -1) dayIndex = days.length - 1; + const clonedDate = new Date(firstDate.getTime()); + clonedDate.setDate(clonedDate.getDate() - dayIndex - 1); + // Padding first week for(let i = 0; i < dayIndex; ++i) { - const el = document.createElement('span'); - this.month.append(el); + if(this.options.showOverflowMonths) { + clonedDate.setDate(clonedDate.getDate() + 1); + this.month.append(this.renderElement(true, '' + clonedDate.getDate())); + } else { + this.month.append(this.renderElement(true)); + } } do { const date = firstDate.getDate(); - const el = document.createElement('button'); - el.classList.add('btn-icon'); - el.innerText = '' + date; + const el = this.renderElement(firstDate > this.maxDate || firstDate < this.minDate, '' + date); el.dataset.timestamp = '' + firstDate.getTime(); - if(firstDate > this.maxDate || firstDate < this.minDate) { - el.setAttribute('disabled', 'true'); - } - if(firstDate.getTime() === this.selectedDate.getTime()) { this.selectedEl = el; el.classList.add('active'); @@ -301,6 +319,14 @@ export default class PopupDatePicker extends PopupElement { firstDate.setDate(date + 1); } while(firstDate.getDate() !== 1); + const remainder = this.month.childElementCount % 7; + if(this.options.showOverflowMonths && remainder) { + for(let i = remainder; i < 7; ++i) { + this.month.append(this.renderElement(true, '' + firstDate.getDate())); + firstDate.setDate(firstDate.getDate() + 1); + } + } + this.container.classList.toggle('is-max-lines', (this.month.childElementCount / 7) > 6); this.monthsContainer.append(this.month); diff --git a/src/components/popups/deleteMessages.ts b/src/components/popups/deleteMessages.ts index 5bb38d16..f1c4d25b 100644 --- a/src/components/popups/deleteMessages.ts +++ b/src/components/popups/deleteMessages.ts @@ -11,7 +11,7 @@ export default class PopupDeleteMessages { const firstName = appPeersManager.getPeerTitle(peerId, false, true); mids = mids.slice(); - const callback = (revoke: boolean) => { + const callback = (revoke?: true) => { onConfirm && onConfirm(); if(type === 'scheduled') { appMessagesManager.deleteScheduledMessages(peerId, mids); @@ -28,13 +28,13 @@ export default class PopupDeleteMessages { buttons = [{ text: 'DELETE', isDanger: true, - callback: () => callback(false) + callback: () => callback() }]; } else { buttons = [{ text: 'DELETE JUST FOR ME', isDanger: true, - callback: () => callback(false) + callback: () => callback() }]; if(peerId > 0) { diff --git a/src/components/popups/newMedia.ts b/src/components/popups/newMedia.ts index c82463c7..fd5c0e3c 100644 --- a/src/components/popups/newMedia.ts +++ b/src/components/popups/newMedia.ts @@ -162,20 +162,26 @@ export default class PopupNewMedia extends PopupElement { this.chat.appMessagesManager.sendAlbum(peerId, w.sendFileDetails.map(d => d.file), Object.assign({ caption, replyToMsgId: input.replyToMsgId, + threadId: this.chat.threadId, isMedia: willAttach.isMedia, silent, scheduleDate }, w)); caption = undefined; - input.replyToMsgId = undefined; + input.replyToMsgId = this.chat.threadId; } } else { if(caption) { if(willAttach.sendFileDetails.length > 1) { - this.chat.appMessagesManager.sendText(peerId, caption, {replyToMsgId: input.replyToMsgId, silent, scheduleDate}); + this.chat.appMessagesManager.sendText(peerId, caption, { + replyToMsgId: input.replyToMsgId, + threadId: this.chat.threadId, + silent, + scheduleDate + }); caption = ''; - input.replyToMsgId = undefined; + //input.replyToMsgId = undefined; } } @@ -185,14 +191,16 @@ export default class PopupNewMedia extends PopupElement { isMedia: willAttach.isMedia, caption, replyToMsgId: input.replyToMsgId, + threadId: this.chat.threadId, silent, scheduleDate }, params)); caption = ''; - input.replyToMsgId = undefined; return promise; }); + + input.replyToMsgId = this.chat.threadId; } //Promise.all(promises); diff --git a/src/components/popups/schedule.ts b/src/components/popups/schedule.ts index 1171cbbf..c7671fff 100644 --- a/src/components/popups/schedule.ts +++ b/src/components/popups/schedule.ts @@ -21,7 +21,8 @@ export default class PopupSchedule extends PopupDatePicker { date.setDate(date.getDate() - 1); return date; })(), - withTime: true + withTime: true, + showOverflowMonths: true }); this.element.classList.add('popup-schedule'); diff --git a/src/components/sidebarLeft/tabs/addMembers.ts b/src/components/sidebarLeft/tabs/addMembers.ts index 95009bb4..c73a608c 100644 --- a/src/components/sidebarLeft/tabs/addMembers.ts +++ b/src/components/sidebarLeft/tabs/addMembers.ts @@ -29,7 +29,7 @@ export default class AppAddMembersTab implements SliderTab { return; } - this.nextBtn.classList.remove('tgico-next'); + this.nextBtn.classList.remove('tgico-arrow-next'); this.nextBtn.disabled = true; putPreloader(this.nextBtn); this.selector.freezed = true; @@ -61,7 +61,7 @@ export default class AppAddMembersTab implements SliderTab { this.nextBtn.innerHTML = ''; this.nextBtn.disabled = false; - this.nextBtn.classList.add('tgico-next'); + this.nextBtn.classList.add('tgico-arrow-next'); this.nextBtn.classList.toggle('is-visible', skippable); appSidebarLeft.selectTab(AppSidebarLeft.SLIDERITEMSIDS.addMembers); diff --git a/src/components/wrappers.ts b/src/components/wrappers.ts index 2f9ba120..ee544e1a 100644 --- a/src/components/wrappers.ts +++ b/src/components/wrappers.ts @@ -351,7 +351,7 @@ export function wrapDocument({message, withTime, uploading, fontWeight}: { }): HTMLElement { if(!fontWeight) fontWeight = 500; - const doc = message.media.document; + const doc = message.media.document || message.media.webpage.document; if(doc.type == 'audio' || doc.type == 'voice') { const audioElement = new AudioElement(); audioElement.setAttribute('message-id', '' + message.mid); diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index fa350e77..cf22e268 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -455,9 +455,9 @@ export class AppImManager { appSidebarRight.sharedMediaTab.loadSidebarMedia(true); appSidebarRight.sharedMediaTab.fillProfileElements(); - setTimeout(() => { + /* setTimeout(() => { appSidebarRight.sharedMediaTab.loadSidebarMedia(false); - }); + }); */ } setTimeout(() => { diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index d0841768..01633070 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -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, 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, 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"; @@ -43,8 +43,11 @@ export type HistoryStorage = { history: number[], pending: number[], + maxId?: number, readPromise?: Promise, readMaxId?: number, + readOutboxMaxId?: number, + maxOutId?: number, reply_markup?: any }; @@ -89,6 +92,11 @@ export class AppMessagesManager { public historiesStorage: { [peerId: string]: HistoryStorage } = {}; + public threadsStorage: { + [peerId: string]: { + [threadId: string]: HistoryStorage + } + } = {}; public searchesStorage: { [peerId: string]: Partial<{ [inputFilter in MyInputMessagesFilter]: { @@ -386,6 +394,7 @@ export class AppMessagesManager { public sendText(peerId: number, text: string, options: Partial<{ entities: any[], replyToMsgId: number, + threadId: number, viaBotId: number, queryId: string, resultId: string, @@ -462,7 +471,7 @@ export class AppMessagesManager { date: options.scheduleDate || (tsNow(true) + serverTimeManager.serverTimeOffset), message: text, random_id: randomIdS, - reply_to: {reply_to_msg_id: replyToMsgId}, + reply_to: this.generateReplyHeader(options.replyToMsgId, options.threadId), via_bot_id: options.viaBotId, reply_markup: options.reply_markup, entities: entities, @@ -579,6 +588,7 @@ export class AppMessagesManager { isMedia: true, replyToMsgId: number, + threadId: number, caption: string, entities: MessageEntity[], width: number, @@ -785,7 +795,7 @@ export class AppMessagesManager { document: file } : media, random_id: randomIdS, - reply_to: {reply_to_msg_id: replyToMsgId}, + reply_to: this.generateReplyHeader(options.replyToMsgId, options.threadId), views: asChannel && 1, pending: true }; @@ -923,6 +933,7 @@ export class AppMessagesManager { isMedia: true, entities: MessageEntity[], replyToMsgId: number, + threadId: number, caption: string, sendFileDetails: Partial<{ duration: number, @@ -958,13 +969,15 @@ export class AppMessagesManager { isMedia: options.isMedia, scheduleDate: options.scheduleDate, silent: options.silent, + replyToMsgId, + threadId: options.threadId, ...details }; if(idx === 0) { o.caption = caption; o.entities = entities; - o.replyToMsgId = replyToMsgId; + //o.replyToMsgId = replyToMsgId; } return this.sendFile(peerId, file, o).message; @@ -1058,6 +1071,7 @@ export class AppMessagesManager { public sendOther(peerId: number, inputMedia: any, options: Partial<{ replyToMsgId: number, + threadId: number, viaBotId: number, reply_markup: any, clearDraft: true, @@ -1181,7 +1195,7 @@ export class AppMessagesManager { message: '', media, random_id: randomIdS, - reply_to: {reply_to_msg_id: replyToMsgId}, + reply_to: this.generateReplyHeader(options.replyToMsgId, options.threadId), via_bot_id: options.viaBotId, reply_markup: options.reply_markup, views: asChannel && 1, @@ -1299,6 +1313,19 @@ export class AppMessagesManager { } } + private generateReplyHeader(replyToMsgId: number, replyToTopId?: number) { + const header = { + _: 'messageReplyHeader', + reply_to_msg_id: replyToMsgId, + } as MessageReplyHeader; + + if(replyToTopId && replyToTopId !== replyToMsgId) { + header.reply_to_top_id = replyToTopId; + } + + return header; + } + private setDialogIndexByMessage(dialog: MTDialog.dialog, message: MyMessage) { if(!dialog.pFlags.pinned || !dialog.index) { dialog.index = this.dialogsStorage.generateDialogIndex(message.date); @@ -1309,6 +1336,9 @@ export class AppMessagesManager { const dialog = this.getDialogByPeerId(message.peerId)[0]; if(dialog) { dialog.top_message = message.mid; + + const historyStorage = this.getHistoryStorage(message.peerId); + historyStorage.maxId = message.mid; this.setDialogIndexByMessage(dialog, message); @@ -1436,6 +1466,11 @@ export class AppMessagesManager { }); } + public isHistoryUnread(peerId: number, threadId?: number) { + const historyStorage = this.getHistoryStorage(peerId, threadId); + return (peerId > 0 ? Math.max(historyStorage.readMaxId, historyStorage.readOutboxMaxId) : historyStorage.readMaxId) < historyStorage.maxId; + } + public getTopMessages(limit: number, folderId: number): Promise { const dialogs = this.dialogsStorage.getFolder(folderId); let offsetId = 0; @@ -1492,7 +1527,7 @@ export class AppMessagesManager { (dialogsResult.dialogs as Dialog[]).forEachReverse(dialog => { //const d = Object.assign({}, dialog); // ! нужно передавать folderId, так как по папке != 0 нет свойства folder_id - this.saveConversation(dialog, folderId); + this.saveConversation(dialog, dialog.folder_id ?? folderId); /* if(dialog.peerId == -1213511294) { this.log.error('lun bot', folderId, d); @@ -1505,9 +1540,11 @@ export class AppMessagesManager { // ! это может случиться, если запрос идёт не по папке 0, а по 1. почему-то read'ов нет // ! в итоге, чтобы получить 1 диалог, делается первый запрос по папке 0, потом запрос для архивных по папке 1, и потом ещё перезагрузка архивного диалога - if(!dialog.read_inbox_max_id && !dialog.read_outbox_max_id) { + if(!this.getLocalMessageId(dialog.read_inbox_max_id) && !this.getLocalMessageId(dialog.read_outbox_max_id)) { noIdsDialogs[dialog.peerId] = dialog; + this.log.error('noIdsDialogs', dialog); + /* if(dialog.peerId == -1213511294) { this.log.error('lun bot', folderId); } */ @@ -1622,7 +1659,7 @@ export class AppMessagesManager { public getMessageById(messageId: number) { for(const peerId in this.messagesStorageByPeerId) { - if(appPeersManager.isChannel(-+peerId)) { + if(appPeersManager.isChannel(+peerId)) { continue; } @@ -1849,7 +1886,7 @@ export class AppMessagesManager { public generateMessageId(messageId: number, temp = false) { const q = 0xFFFFFFFF; const num = temp ? ++this.tempNum : 0; - if(messageId > q) { + if(messageId >= q) { if(temp) { return messageId + (num & 0xFFFF); } @@ -1865,7 +1902,7 @@ export class AppMessagesManager { */ public getLocalMessageId(messageId: number) { const q = 0xFFFFFFFF; - if(messageId < q) { + if(messageId <= q) { return messageId; } @@ -1928,9 +1965,17 @@ export class AppMessagesManager { } // this.log(dT(), 'msg unread', mid, apiMessage.pFlags.out, dialog && dialog[apiMessage.pFlags.out ? 'read_outbox_max_id' : 'read_inbox_max_id']) - if(message.reply_to && message.reply_to.reply_to_msg_id) { - //message.reply_to_mid = message.reply_to.reply_to_msg_id; - message.reply_to_mid = this.generateMessageId(message.reply_to.reply_to_msg_id); + if(message.reply_to) { + if(message.reply_to.reply_to_msg_id) { + message.reply_to.reply_to_msg_id = message.reply_to_mid = this.generateMessageId(message.reply_to.reply_to_msg_id); + } + + if(message.reply_to.reply_to_top_id) message.reply_to.reply_to_top_id = this.generateMessageId(message.reply_to.reply_to_top_id); + } + + if(message.replies) { + if(message.replies.max_id) message.replies.max_id = this.generateMessageId(message.replies.max_id); + if(message.replies.read_max_id) message.replies.read_max_id = this.generateMessageId(message.replies.read_max_id); } const overwriting = !!message.peerId; @@ -1945,7 +1990,8 @@ export class AppMessagesManager { if(message.peerId == myId/* && !message.from_id && !message.fwd_from */) { message.fromId = message.fwd_from ? (message.fwd_from.from_id ? appPeersManager.getPeerId(message.fwd_from.from_id) : 0) : myId; } else { - message.fromId = message.pFlags.post || (!message.pFlags.out && !message.from_id) ? peerId : appPeersManager.getPeerId(message.from_id); + //message.fromId = message.pFlags.post || (!message.pFlags.out && !message.from_id) ? peerId : appPeersManager.getPeerId(message.from_id); + message.fromId = message.pFlags.post || !message.from_id ? peerId : appPeersManager.getPeerId(message.from_id); } const fwdHeader = message.fwd_from; @@ -2449,6 +2495,7 @@ export class AppMessagesManager { if(!topMessage || (this.getMessageByPeer(peerId, topPendingMessage) as MyMessage).date > (this.getMessageByPeer(peerId, topMessage) as MyMessage).date) { dialog.top_message = topMessage = topPendingMessage; + this.getHistoryStorage(peerId).maxId = topPendingMessage; } } @@ -2546,14 +2593,15 @@ export class AppMessagesManager { } } + const wasDialogBefore = this.getDialogByPeerId(peerId)[0]; + dialog.top_message = mid; - dialog.read_inbox_max_id = this.generateMessageId(dialog.read_inbox_max_id); - dialog.read_outbox_max_id = this.generateMessageId(dialog.read_outbox_max_id); + dialog.read_inbox_max_id = wasDialogBefore && !dialog.read_inbox_max_id ? wasDialogBefore.read_inbox_max_id : this.generateMessageId(dialog.read_inbox_max_id); + dialog.read_outbox_max_id = wasDialogBefore && !dialog.read_outbox_max_id ? wasDialogBefore.read_outbox_max_id : this.generateMessageId(dialog.read_outbox_max_id); if(!dialog.hasOwnProperty('folder_id')) { if(dialog._ == 'dialog') { // ! СЛОЖНО ! СМОТРИ В getTopMessages - const wasDialogBefore = this.getDialogByPeerId(peerId)[0]; dialog.folder_id = wasDialogBefore ? wasDialogBefore.folder_id : folderId; }/* else if(dialog._ == 'dialogFolder') { dialog.folder_id = dialog.folder.id; @@ -2562,12 +2610,9 @@ export class AppMessagesManager { dialog.peerId = peerId; - this.dialogsStorage.generateIndexForDialog(dialog); - this.dialogsStorage.pushDialog(dialog, message.date); - // Because we saved message without dialog present - if(message.mid > 0) { - if(message.mid > dialog[message.pFlags.out ? 'read_outbox_max_id' : 'read_inbox_max_id']) message.pFlags.unread = true; + if(mid > 0) { + if(mid > dialog[message.pFlags.out ? 'read_outbox_max_id' : 'read_inbox_max_id']) message.pFlags.unread = true; else delete message.pFlags.unread; } @@ -2584,13 +2629,16 @@ export class AppMessagesManager { historyStorage[mid > 0 ? 'history' : 'pending'].push(mid); } + historyStorage.maxId = mid; + historyStorage.readMaxId = dialog.read_inbox_max_id; + historyStorage.readOutboxMaxId = dialog.read_outbox_max_id; + if(channelId && dialog.pts) { apiUpdatesManager.addChannelState(channelId, dialog.pts); } - //if(this.filtersStorage.inited) { - //this.filtersStorage.processDialog(dialog); - //} + this.dialogsStorage.generateIndexForDialog(dialog); + this.dialogsStorage.pushDialog(dialog, message.date); } public mergeReplyKeyboard(historyStorage: HistoryStorage, message: any) { @@ -2679,9 +2727,24 @@ export class AppMessagesManager { }); } + public getSearchNew({peerId, query, inputFilter, maxId, limit, offsetRate, backLimit, threadId}: { + peerId: number, + maxId: number, + limit?: number, + offsetRate?: number, + backLimit?: number, + threadId?: number, + query?: string, + inputFilter?: { + _: MyInputMessagesFilter + }, + }) { + return this.getSearch(peerId, query, inputFilter, maxId, limit, offsetRate, backLimit, threadId); + } + public getSearch(peerId = 0, query: string = '', inputFilter: { _: MyInputMessagesFilter - } = {_: 'inputMessagesFilterEmpty'}, maxId: number, limit = 20, offsetRate = 0, backLimit = 0): Promise<{ + } = {_: 'inputMessagesFilterEmpty'}, maxId: number, limit = 20, offsetRate = 0, backLimit = 0, threadId = 0): Promise<{ count: number, next_rate: number, offset_id_offset: number, @@ -2704,7 +2767,7 @@ export class AppMessagesManager { }; // * костыль для limit 1, если нужно и получить сообщение, и узнать количество сообщений - if(peerId && !backLimit && !maxId && !query && limit !== 1/* && inputFilter._ !== 'inputMessagesFilterPinned' */) { + if(peerId && !backLimit && !maxId && !query && limit !== 1 && !threadId/* && inputFilter._ !== 'inputMessagesFilterPinned' */) { storage = beta ? this.getSearchStorage(peerId, inputFilter._) : this.getHistoryStorage(peerId); @@ -2860,19 +2923,20 @@ export class AppMessagesManager { add_offset: backLimit ? -backLimit : 0, max_id: 0, min_id: 0, - hash: 0 + hash: 0, + top_msg_id: threadId }, { //timeout: APITIMEOUT, noErrorBox: true }); } else { - var offsetDate = 0; - var offsetPeerId = 0; - var offsetId = 0; - var offsetMessage = maxId && this.getMessageByPeer(peerId, maxId); + //var offsetDate = 0; + let offsetPeerId = 0; + let offsetId = 0; + let offsetMessage = maxId && this.getMessageByPeer(peerId, maxId); if(offsetMessage && offsetMessage.date) { - offsetDate = offsetMessage.date + serverTimeManager.serverTimeOffset; + //offsetDate = offsetMessage.date + serverTimeManager.serverTimeOffset; offsetId = offsetMessage.id; offsetPeerId = this.getMessagePeer(offsetMessage); } @@ -2885,7 +2949,7 @@ export class AppMessagesManager { offset_rate: offsetRate, offset_peer: appPeersManager.getInputPeerById(offsetPeerId), offset_id: offsetId, - limit + limit, }, { //timeout: APITIMEOUT, noErrorBox: true @@ -2930,6 +2994,25 @@ export class AppMessagesManager { }); } + public getDiscussionMessage(peerId: number, mid: number) { + return apiManager.invokeApi('messages.getDiscussionMessage', { + peer: appPeersManager.getInputPeerById(peerId), + msg_id: this.getLocalMessageId(mid) + }).then(result => { + appChatsManager.saveApiChats(result.chats); + appUsersManager.saveApiUsers(result.users); + this.saveMessages(result.messages); + + const message = result.messages[0] as MyMessage; + const historyStorage = this.getHistoryStorage(message.peerId, message.mid); + historyStorage.maxId = this.generateMessageId(result.max_id) || 0; + historyStorage.readMaxId = this.generateMessageId(result.read_inbox_max_id) || 0; + historyStorage.readOutboxMaxId = this.generateMessageId(result.read_outbox_max_id) || 0; + + return result; + }); + } + handleNewMessages = () => { clearTimeout(this.newMessagesHandlePromise); this.newMessagesHandlePromise = 0; @@ -2972,12 +3055,12 @@ export class AppMessagesManager { } } - public deleteMessages(peerId: number, mids: number[], revoke: boolean) { + public deleteMessages(peerId: number, mids: number[], revoke?: true) { let promise: Promise; mids = mids.map(mid => this.getLocalMessageId(mid)); - if(peerId < 0 && appPeersManager.isChannel(-peerId)) { + if(peerId < 0 && appPeersManager.isChannel(peerId)) { const channelId = -peerId; const channel = appChatsManager.getChat(channelId); if(!channel.pFlags.creator && !(channel.pFlags.editor && channel.pFlags.megagroup)) { @@ -3015,7 +3098,7 @@ export class AppMessagesManager { }); } else { promise = apiManager.invokeApi('messages.deleteMessages', { - revoke: revoke || undefined, + revoke, id: mids }).then((affectedMessages) => { apiUpdatesManager.processUpdateMessage({ @@ -3035,24 +3118,10 @@ export class AppMessagesManager { public readHistory(peerId: number, maxId = 0) { // console.trace('start read') + if(!this.isHistoryUnread(peerId)) return Promise.resolve(true); + const isChannel = appPeersManager.isChannel(peerId); const historyStorage = this.getHistoryStorage(peerId); - const foundDialog = this.getDialogByPeerId(peerId)[0]; - - if(!foundDialog || !foundDialog.unread_count) { - if(!historyStorage.history.length) { - return Promise.resolve(false); - } - - let foundUnread = !!historyStorage.history.find(messageId => { - const message = this.getMessagesStorage(peerId)[messageId]; - return message && !message.pFlags.out && message.pFlags.unread; - }); - - if(!foundUnread) { - return Promise.resolve(false); - } - } if(!historyStorage.readMaxId || maxId > historyStorage.readMaxId) { historyStorage.readMaxId = maxId; @@ -3066,7 +3135,7 @@ export class AppMessagesManager { if(isChannel) { apiPromise = apiManager.invokeApi('channels.readHistory', { channel: appChatsManager.getChannelInput(-peerId), - max_id: maxId + max_id: this.getLocalMessageId(maxId) }).then((res) => { apiUpdatesManager.processUpdateMessage({ _: 'updateShort', @@ -3082,7 +3151,7 @@ export class AppMessagesManager { } else { apiPromise = apiManager.invokeApi('messages.readHistory', { peer: appPeersManager.getInputPeerById(peerId), - max_id: maxId + max_id: this.getLocalMessageId(maxId) }).then((affectedMessages) => { apiUpdatesManager.processUpdateMessage({ _: 'updateShort', @@ -3111,8 +3180,6 @@ export class AppMessagesManager { if(historyStorage.readMaxId > maxId) { this.readHistory(peerId, historyStorage.readMaxId); - } else { - delete historyStorage.readMaxId; } }); @@ -3152,7 +3219,13 @@ export class AppMessagesManager { } } - public getHistoryStorage(peerId: number) { + public getHistoryStorage(peerId: number, threadId?: number) { + 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.historiesStorage[peerId] ?? (this.historiesStorage[peerId] = {count: null, history: [], pending: []}); } @@ -3427,7 +3500,6 @@ export class AppMessagesManager { case 'updateEditChannelMessage': { const message = update.message as MyMessage; const peerId = this.getMessagePeer(message); - //const mid = message.id; const mid = this.generateMessageId(message.id); const storage = this.getMessagesStorage(peerId); if(storage[mid] === undefined) { @@ -3456,22 +3528,7 @@ export class AppMessagesManager { mid }); - const groupId = (message as Message.message).grouped_id; - /* if(this.pinnedMessagesStorage[peerId]) { - let pinnedMid: number; - if(groupId) { - const mids = this.getMidsByAlbum(groupId); - pinnedMid = mids.find(mid => this.pinnedMessagesStorage[peerId] == mid); - } else if(this.pinnedMessagesStorage[peerId] == mid) { - pinnedMid = mid; - } - - if(pinnedMid) { - rootScope.broadcast('peer_pinned_message', peerId); - } - } */ - - if(isTopMessage || groupId) { + if(isTopMessage || (message as Message.message).grouped_id) { const updatedDialogs: {[peerId: number]: Dialog} = {}; updatedDialogs[peerId] = dialog; rootScope.broadcast('dialogs_multiupdate', updatedDialogs); @@ -3480,27 +3537,34 @@ export class AppMessagesManager { break; } + case 'updateReadChannelDiscussionInbox': + case 'updateReadChannelDiscussionOutbox': case 'updateReadHistoryInbox': case 'updateReadHistoryOutbox': case 'updateReadChannelInbox': case 'updateReadChannelOutbox': { - const channelId: number = (update as Update.updateReadChannelInbox).channel_id; - //const maxId = update.max_id; - const maxId = this.generateMessageId(update.max_id); + const channelId = (update as Update.updateReadChannelInbox).channel_id; + const maxId = this.generateMessageId((update as Update.updateReadChannelInbox).max_id || (update as Update.updateReadChannelDiscussionInbox).read_max_id); + const threadId = this.generateMessageId((update as Update.updateReadChannelDiscussionInbox).top_msg_id); const peerId = channelId ? -channelId : appPeersManager.getPeerId((update as Update.updateReadHistoryInbox).peer); - const isOut = update._ == 'updateReadHistoryOutbox' || update._ == 'updateReadChannelOutbox' ? true : undefined; + + const isOut = update._ === 'updateReadHistoryOutbox' || update._ === 'updateReadChannelOutbox' || update._ === 'updateReadChannelDiscussionOutbox' ? true : undefined; + + const storage = this.getMessagesStorage(peerId); + const history = getObjectKeysAndSort(storage, 'desc'); const foundDialog = this.getDialogByPeerId(peerId)[0]; - const history = getObjectKeysAndSort(this.getMessagesStorage(peerId), 'desc'); + const stillUnreadCount = (update as Update.updateReadChannelInbox).still_unread_count; let newUnreadCount = 0; let foundAffected = false; //this.log.warn(dT(), 'read', peerId, isOut ? 'out' : 'in', maxId) + const historyStorage = this.getHistoryStorage(peerId, threadId); + if(peerId > 0 && isOut) { appUsersManager.forceUserOnline(peerId); } - const storage = this.getMessagesStorage(peerId); for(let i = 0, length = history.length; i < length; i++) { const messageId = history[i]; if(messageId > maxId) { @@ -3508,9 +3572,6 @@ export class AppMessagesManager { } const message = storage[messageId]; - if(!message) { - continue; - } if(message.pFlags.out != isOut) { continue; @@ -3519,6 +3580,13 @@ export class AppMessagesManager { if(!message.pFlags.unread) { break; } + + if(threadId) { + const replyTo = message.reply_to as MessageReplyHeader; + if(!replyTo || (replyTo.reply_to_top_id || replyTo.reply_to_msg_id) !== threadId) { + continue; + } + } // this.log.warn('read', messageId, message.pFlags.unread, message) if(message.pFlags.unread) { @@ -3527,28 +3595,30 @@ export class AppMessagesManager { foundAffected = true; } - if(!message.pFlags.out) { - if(foundDialog) { - newUnreadCount = --foundDialog.unread_count; - } + if(!message.pFlags.out && !threadId && stillUnreadCount === undefined) { + newUnreadCount = --foundDialog.unread_count; //NotificationsManager.cancel('msg' + messageId); // warning } } } - if(foundDialog) { - if(!isOut && newUnreadCount && foundDialog.top_message <= maxId) { - newUnreadCount = foundDialog.unread_count = 0; - } + if(isOut) historyStorage.readOutboxMaxId = maxId; + else historyStorage.readMaxId = maxId; - foundDialog[isOut ? 'read_outbox_max_id' : 'read_inbox_max_id'] = maxId; - } + if(!threadId && foundDialog) { + if(isOut) foundDialog.read_outbox_max_id = maxId; + else foundDialog.read_inbox_max_id = maxId; - // need be commented for read out messages - //if(newUnreadCount != 0 || !isOut) { // fix 16.11.2019 (maybe not) - //////////this.log.warn(dT(), 'cnt', peerId, newUnreadCount, isOut, foundDialog, update, foundAffected); - rootScope.broadcast('dialog_unread', {peerId, count: newUnreadCount}); - //} + if(!isOut) { + if(newUnreadCount < 0 || !this.isHistoryUnread(peerId)) { + foundDialog.unread_count = 0; + } else if(newUnreadCount && foundDialog.top_message > maxId) { + foundDialog.unread_count = newUnreadCount; + } + } + + rootScope.broadcast('dialog_unread', {peerId}); + } if(foundAffected) { rootScope.broadcast('messages_read'); @@ -3625,10 +3695,7 @@ export class AppMessagesManager { if(historyUpdated.unread) { foundDialog.unread_count -= historyUpdated.unread; - rootScope.broadcast('dialog_unread', { - peerId, - count: foundDialog.unread_count - }); + rootScope.broadcast('dialog_unread', {peerId}); } if(historyUpdated.msgs[foundDialog.top_message]) { @@ -4061,12 +4128,16 @@ export class AppMessagesManager { }); } - public getHistory(peerId: number, maxId = 0, limit: number, backLimit?: number) { + public getHistory(peerId: number, maxId = 0, limit: number, backLimit?: number, threadId?: number) { + if(threadId) { + threadId = this.getLocalMessageId(threadId); + } + if(this.migratedFromTo[peerId]) { peerId = this.migratedFromTo[peerId]; } - const historyStorage = this.getHistoryStorage(peerId); + const historyStorage = this.getHistoryStorage(peerId, threadId); const unreadOffset = 0; const unreadSkip = false; @@ -4126,7 +4197,7 @@ export class AppMessagesManager { limit += backLimit; } - return this.requestHistory(reqPeerId, maxId, limit, offset).then((historyResult) => { + return this.requestHistory(reqPeerId, maxId, limit, offset, undefined, threadId).then((historyResult) => { historyStorage.count = (historyResult as MessagesMessages.messagesMessagesSlice).count || historyResult.messages.length; if(isMigrated) { historyStorage.count++; @@ -4150,7 +4221,7 @@ export class AppMessagesManager { }); } - return this.fillHistoryStorage(peerId, maxId, limit, historyStorage).then(() => { + return this.fillHistoryStorage(peerId, maxId, limit, historyStorage, threadId).then(() => { offset = 0; if(maxId > 0) { for(offset = 0; offset < historyStorage.history.length; offset++) { @@ -4174,10 +4245,10 @@ export class AppMessagesManager { }); } - public fillHistoryStorage(peerId: number, maxId: number, fullLimit: number, historyStorage: HistoryStorage): Promise { + public fillHistoryStorage(peerId: number, maxId: number, fullLimit: number, historyStorage: HistoryStorage, threadId?: number): Promise { // this.log('fill history storage', peerId, maxId, fullLimit, angular.copy(historyStorage)) const offset = (this.migratedFromTo[peerId] && !maxId) ? 1 : 0; - return this.requestHistory(peerId, maxId, fullLimit, offset).then((historyResult) => { + return this.requestHistory(peerId, maxId, fullLimit, offset, undefined, threadId).then((historyResult) => { historyStorage.count = (historyResult as MessagesMessages.messagesMessagesSlice).count || historyResult.messages.length; if(!maxId && historyResult.messages.length) { @@ -4228,9 +4299,9 @@ export class AppMessagesManager { } } - return this.fillHistoryStorage(peerId, maxId, fullLimit, historyStorage); + return this.fillHistoryStorage(peerId, maxId, fullLimit, historyStorage, threadId); } else if(totalCount < historyStorage.count) { - return this.fillHistoryStorage(peerId, maxId, fullLimit, historyStorage); + return this.fillHistoryStorage(peerId, maxId, fullLimit, historyStorage, threadId); } } @@ -4251,15 +4322,16 @@ export class AppMessagesManager { return result; } - public requestHistory(peerId: number, maxId: number, limit = 0, offset = 0, offsetDate = 0): Promise> { + public requestHistory(peerId: number, maxId: number, limit = 0, offset = 0, offsetDate = 0, threadId = 0): Promise> { const isChannel = appPeersManager.isChannel(peerId); //console.trace('requestHistory', peerId, maxId, limit, offset); //rootScope.broadcast('history_request'); - - const promise = apiManager.invokeApi('messages.getHistory', { + + const options = { peer: appPeersManager.getInputPeerById(peerId), + msg_id: threadId, offset_id: this.getLocalMessageId(maxId) || 0, offset_date: offsetDate, add_offset: offset, @@ -4267,18 +4339,16 @@ export class AppMessagesManager { max_id: 0, min_id: 0, hash: 0 - }, { + }; + + const promise: ReturnType = apiManager.invokeApi(threadId ? 'messages.getReplies' : 'messages.getHistory', options, { //timeout: APITIMEOUT, noErrorBox: true - }) as ReturnType; + }) as any; return promise.then((historyResult) => { this.log('requestHistory result:', peerId, historyResult, maxId, limit, offset); - /* if(historyResult._ == 'messages.messagesNotModified') { - return historyResult as any; - } */ - appUsersManager.saveApiUsers(historyResult.users); appChatsManager.saveApiChats(historyResult.chats); this.saveMessages(historyResult.messages); @@ -4295,47 +4365,16 @@ export class AppMessagesManager { } // will load more history if last message is album grouped (because it can be not last item) - const historyStorage = this.getHistoryStorage(peerId); + const historyStorage = this.getHistoryStorage(peerId, threadId); // historyResult.messages: desc sorted if(length && (historyResult.messages[length - 1] as Message.message).grouped_id && (historyStorage.history.length + historyResult.messages.length) < (historyResult as MessagesMessages.messagesMessagesSlice).count) { - return this.requestHistory(peerId, (historyResult.messages[length - 1] as Message.message).mid, 10, 0).then((_historyResult) => { + return this.requestHistory(peerId, (historyResult.messages[length - 1] as Message.message).mid, 10, 0, offsetDate, threadId).then((_historyResult) => { return historyResult; }); } - // don't need the intro now - /* if(peerId < 0 || !appUsersManager.isBot(peerId) || (length == limit && limit < historyResult.count)) { - return historyResult; - } */ return historyResult as any; - - /* return appProfileManager.getProfile(peerId).then((userFull: any) => { - var description = userFull.bot_info && userFull.bot_info.description; - if(description) { - var messageId = this.tempId--; - var message = { - _: 'messageService', - id: messageId, - from_id: peerId, - peer_id: appPeersManager.getOutputPeer(peerId), - pFlags: {}, - date: tsNow(true) + serverTimeManager.serverTimeOffset, - action: { - _: 'messageActionBotIntro', - description: description - } - } - - this.saveMessages([message]); - historyResult.messages.push(message); - if(historyResult.count) { - historyResult.count++; - } - } - - return historyResult; - }); */ }, (error) => { switch (error.type) { case 'CHANNEL_PRIVATE': diff --git a/src/lib/rootScope.ts b/src/lib/rootScope.ts index a98edc2f..1be9bdce 100644 --- a/src/lib/rootScope.ts +++ b/src/lib/rootScope.ts @@ -20,7 +20,7 @@ type BroadcastEvents = { 'filter_order': number[], 'dialog_draft': {peerId: number, draft: any, index: number}, - 'dialog_unread': {peerId: number, count?: number}, + 'dialog_unread': {peerId: number}, 'dialog_flush': {peerId: number}, 'dialog_drop': {peerId: number, dialog?: Dialog}, 'dialog_migrate': {migrateFrom: number, migrateTo: number}, diff --git a/src/scss/partials/_avatar.scss b/src/scss/partials/_avatar.scss index 155442e1..bfab519c 100644 --- a/src/scss/partials/_avatar.scss +++ b/src/scss/partials/_avatar.scss @@ -107,4 +107,9 @@ avatar-element { --size: 32px; --multiplier: 1.6875; } + + &.avatar-18 { + --size: 18px; + --multiplier: 3; + } } \ No newline at end of file diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index a55b50d8..6af7f5fa 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -714,7 +714,8 @@ $bubble-margin: .25rem; } .web { - margin-top: -6px !important; + padding-top: 1px; + margin: 4px 0 -5px 1px; // margin-bottom: 5px; max-width: 100%; overflow: hidden; @@ -775,11 +776,6 @@ $bubble-margin: .25rem; .web, .reply { font-size: .95rem; - // margin: .25rem; - margin: 4px 4px 0 6px; - //padding: .25rem; - padding: 4px; - border-radius: 4px; //transition: anim(background-color); /* &:hover { @@ -829,9 +825,10 @@ $bubble-margin: .25rem; } .reply { - margin-bottom: 6px; - margin-top: 0; + padding: 4px; + margin: 0 4px 6px 4px; cursor: pointer; + border-radius: 4px; &-content { max-width: 300px; @@ -881,6 +878,7 @@ $bubble-margin: .25rem; line-height: 21px; word-break: break-word; white-space: pre-wrap; // * fix spaces on line begin + position: relative; /* * { overflow: hidden; @@ -1479,6 +1477,70 @@ $bubble-margin: .25rem; audio-element, poll-element { white-space: normal; // * fix due to .message white-space prewrap } + + .replies-footer { + height: 50px; + border-top: 1px solid #dadce0; + position: relative; + display: flex; + align-items: center; + padding: 0 .5rem; + border-bottom-left-radius: inherit; + border-bottom-right-radius: inherit; + color: $color-blue; + min-width: 15rem; + user-select: none; + + .tgico-comments, .tgico-next { + font-size: 1.4375rem; + } + + &-text { + font-weight: 500; + margin-left: 13px; + display: flex; + align-items: center; + } + + &-avatars { + display: flex; + flex-direction: row-reverse; + + avatar-element { + border: 2px solid #fff; + cursor: pointer; + } + } + + .tgico-next { + position: absolute; + right: .5rem; + } + + .rp { + width: 100%; + height: 100%; + position: absolute; + top: 0; + left: 0; + overflow: hidden; + border-radius: inherit; + cursor: pointer; + } + + &.is-unread { + .replies-footer-text { + &:after { + content: " "; + background-color: $color-blue; + width: .5rem; + height: .5rem; + margin-left: .75rem; + border-radius: 50%; + } + } + } + } } // * fix scroll with only 1 bubble @@ -1988,7 +2050,7 @@ $bubble-margin: .25rem; } &.is-link:before { - content: $tgico-next; + content: $tgico-arrow-next; position: absolute; right: 2px; top: 2px; @@ -2251,11 +2313,7 @@ poll-element { } avatar-element { - width: 18px; - height: 18px; border: 1px solid #fff; - line-height: 20px; - font-size: 10px; cursor: pointer; } diff --git a/src/scss/partials/_fonts.scss b/src/scss/partials/_fonts.scss index fa467061..2794ee51 100644 --- a/src/scss/partials/_fonts.scss +++ b/src/scss/partials/_fonts.scss @@ -366,7 +366,7 @@ .tgico-forward:before { content: "\e96d"; } -.tgico-next:before { +.tgico-arrow-next:before { content: "\e96e"; } .tgico-unlock:before { diff --git a/src/scss/partials/_ico.scss b/src/scss/partials/_ico.scss index c2c10af8..0bcc1a15 100644 --- a/src/scss/partials/_ico.scss +++ b/src/scss/partials/_ico.scss @@ -123,7 +123,7 @@ $tgico-play: "\e96a"; $tgico-pause: "\e96b"; $tgico-reply: "\e96c"; $tgico-forward: "\e96d"; -$tgico-next: "\e96e"; +$tgico-arrow-next: "\e96e"; $tgico-unlock: "\e96f"; $tgico-lock: "\e970"; $tgico-data: "\e971"; diff --git a/src/scss/partials/popups/_datePicker.scss b/src/scss/partials/popups/_datePicker.scss index fc20f4ab..5c4293d3 100644 --- a/src/scss/partials/popups/_datePicker.scss +++ b/src/scss/partials/popups/_datePicker.scss @@ -79,15 +79,7 @@ width: 100%; justify-content: center; - span, .btn-icon { - // justify-self: center; - // width: 40px; - // height: 40px; - // font-size: 1rem; - // color: #707579; - // display: flex; - // justify-content: center; - // align-items: center; + .btn-icon { justify-self: center; width: 38px; height: 38px; @@ -96,6 +88,14 @@ display: flex; justify-content: center; align-items: center; + + &:disabled { + opacity: 1; + } + } + + &-date:disabled { + color: #c2c3c4; } .btn-icon:not(:disabled) { @@ -109,4 +109,77 @@ } } } +} + +.popup-schedule { + .popup-header { + justify-content: space-between; + + .btn-icon { + font-size: 22px; + } + + .popup-close { + color: #52585d; + } + } + + .popup-container { + min-width: 420px; + width: 420px; + } + + .date-picker { + &-month { + &-title { + font-weight: 500; + font-size: 20px; + margin-left: -5rem; + } + + .btn-icon { + font-weight: 500; + font-size: 15px; + } + + &-date:disabled { + color: #9ba3a8 !important; + } + + &-day { + font-weight: bold; + color: black !important; + font-size: 14px !important; + } + } + + &-time { + display: flex; + justify-content: center; + margin-bottom: 1.5rem; + + .input-field { + width: 80px; + + &-input { + text-align: center; + } + } + + &-delimiter { + padding: 14px 20px; + } + } + + &-controls { + .btn-icon { + color: #2a8ee4; + + &:disabled { + visibility: visible; + opacity: 1; + } + } + } + } } \ No newline at end of file