diff --git a/src/components/appMediaViewer.ts b/src/components/appMediaViewer.ts index 8de25c45..e72ea384 100644 --- a/src/components/appMediaViewer.ts +++ b/src/components/appMediaViewer.ts @@ -133,7 +133,7 @@ class AppMediaViewerBase { - const button = ButtonIcon(name); + const button = ButtonIcon(name, {noRipple: name === 'close' || undefined}); this.buttons[name] = button; buttonsDiv.append(button); }); diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index f9be0d5c..7ebb8a82 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -42,7 +42,7 @@ import { AppChatsManager } from "../../lib/appManagers/appChatsManager"; import ListenerSetter from "../../helpers/listenerSetter"; import PollElement from "../poll"; import AudioElement from "../audio"; -import { Message, MessageEntity, MessageReplyHeader } from "../../layer"; +import { Message, MessageEntity, MessageReplyHeader, Update } from "../../layer"; import { REPLIES_PEER_ID } from "../../lib/mtproto/mtproto_config"; import { FocusDirection } from "../../helpers/fastSmoothScroll"; import useHeavyAnimationCheck, { getHeavyAnimationPromise, dispatchHeavyAnimationEvent, interruptHeavyAnimation } from "../../hooks/useHeavyAnimationCheck"; @@ -408,7 +408,7 @@ export default class ChatBubbles { } this.listenerSetter.add(this.bubblesContainer, 'dblclick', (e) => { - if(this.chat.selection.isSelecting || !this.appMessagesManager.canWriteToPeer(this.peerId)) { + if(this.chat.selection.isSelecting || !this.appMessagesManager.canWriteToPeer(this.peerId, this.chat.threadId)) { return; } @@ -502,6 +502,19 @@ export default class ChatBubbles { } }); + this.listenerSetter.add(rootScope, 'chat_update', (e) => { + const chatId: number = e; + if(this.peerId === -chatId) { + const hadRights = this.chatInner.classList.contains('has-rights'); + const hasRights = this.appMessagesManager.canWriteToPeer(this.peerId, this.chat.threadId); + + if(hadRights !== hasRights) { + this.finishPeerChange(); + this.chat.input.updateMessageInput(); + } + } + }); + this.unreadedObserver = new IntersectionObserver((entries) => { entries.forEach(entry => { if(entry.isIntersecting) { @@ -1748,7 +1761,7 @@ export default class ChatBubbles { public finishPeerChange() { const peerId = this.peerId; const isChannel = this.appPeersManager.isChannel(peerId); - const canWrite = this.appMessagesManager.canWriteToPeer(peerId); + const canWrite = this.appMessagesManager.canWriteToPeer(peerId, this.chat.threadId); this.chatInner.classList.toggle('has-rights', canWrite); this.bubblesContainer.classList.toggle('is-chat-input-hidden', !canWrite); diff --git a/src/components/chat/contextMenu.ts b/src/components/chat/contextMenu.ts index 600115ff..6f3ff16a 100644 --- a/src/components/chat/contextMenu.ts +++ b/src/components/chat/contextMenu.ts @@ -35,9 +35,9 @@ export default class ChatContextMenu { private isTargetAGroupedItem: boolean; private isTextSelected: boolean; private isAnchorTarget: boolean; - public peerId: number; - public mid: number; - public message: any; + private peerId: number; + private mid: number; + private message: any; constructor(private attachTo: HTMLElement, private chat: Chat, private appMessagesManager: AppMessagesManager, private appChatsManager: AppChatsManager, private appPeersManager: AppPeersManager, private appPollsManager: AppPollsManager) { const onContextMenu = (e: MouseEvent | Touch | TouchEvent) => { @@ -190,7 +190,7 @@ export default class ChatContextMenu { icon: 'reply', text: 'Reply', onClick: this.onReplyClick, - verify: () => this.appMessagesManager.canWriteToPeer(this.peerId) && + verify: () => this.appMessagesManager.canWriteToPeer(this.peerId, this.chat.threadId) && !this.message.pFlags.is_outgoing && !!this.chat.input.messageInput && this.chat.type !== 'scheduled'/* , diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index 5f61a6d0..f6bcaa2e 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -692,31 +692,7 @@ export default class ChatInput { } if(this.messageInput) { - const canWrite = this.appMessagesManager.canWriteToPeer(peerId); - this.chatInput.classList.add('no-transition'); - this.chatInput.classList.toggle('is-hidden', !canWrite); - void this.chatInput.offsetLeft; // reflow - this.chatInput.classList.remove('no-transition'); - - const visible = this.attachMenuButtons.filter(button => { - const good = button.verify(peerId); - button.element.classList.toggle('hide', !good); - return good; - }); - - if(!canWrite) { - this.messageInput.removeAttribute('contenteditable'); - } else { - this.messageInput.setAttribute('contenteditable', 'true'); - this.setDraft(undefined, false); - - if(!this.messageInput.innerHTML) { - this.messageInputField.onFakeInput(); - } - } - - this.attachMenu.toggleAttribute('disabled', !visible.length); - this.updateSendBtn(); + this.updateMessageInput(); } else if(this.pinnedControlBtn) { if(this.appPeersManager.canPinMessage(this.chat.peerId)) { this.pinnedControlBtn.append(i18n('Chat.Input.UnpinAll')); @@ -728,6 +704,34 @@ export default class ChatInput { } } + public updateMessageInput() { + const canWrite = this.appMessagesManager.canWriteToPeer(this.chat.peerId, this.chat.threadId); + this.chatInput.classList.add('no-transition'); + this.chatInput.classList.toggle('is-hidden', !canWrite); + void this.chatInput.offsetLeft; // reflow + this.chatInput.classList.remove('no-transition'); + + const visible = this.attachMenuButtons.filter(button => { + const good = button.verify(this.chat.peerId); + button.element.classList.toggle('hide', !good); + return good; + }); + + if(!canWrite) { + this.messageInput.removeAttribute('contenteditable'); + } else { + this.messageInput.setAttribute('contenteditable', 'true'); + this.setDraft(undefined, false); + + if(!this.messageInput.innerHTML) { + this.messageInputField.onFakeInput(); + } + } + + this.attachMenu.toggleAttribute('disabled', !visible.length); + this.updateSendBtn(); + } + private attachMessageInputField() { const oldInputField = this.messageInputField; this.messageInputField = new InputField({ diff --git a/src/components/chat/topbar.ts b/src/components/chat/topbar.ts index ab10707c..45a117e3 100644 --- a/src/components/chat/topbar.ts +++ b/src/components/chat/topbar.ts @@ -289,9 +289,9 @@ export default class ChatTopbar { }, {listenerSetter: this.listenerSetter}); this.listenerSetter.add(rootScope, 'chat_update', (e) => { - const peerId: number = e; - if(this.peerId === -peerId) { - const chat = this.appChatsManager.getChat(peerId) as Channel/* | Chat */; + const chatId: number = e; + if(this.peerId === -chatId) { + const chat = this.appChatsManager.getChat(chatId) as Channel/* | Chat */; this.btnJoin.classList.toggle('hide', !(chat as Channel)?.pFlags?.left); this.setUtilsWidth(); diff --git a/src/components/popups/deleteDialog.ts b/src/components/popups/deleteDialog.ts index 6a17c5f5..ee3bce4a 100644 --- a/src/components/popups/deleteDialog.ts +++ b/src/components/popups/deleteDialog.ts @@ -24,9 +24,13 @@ export default class PopupDeleteDialog { }; */ const callbackLeave = (checked: PopupPeerButtonCallbackCheckboxes) => { - const promise = appChatsManager.leave(-peerId).then(() => { - return appMessagesManager.flushHistory(-peerId); - }); + let promise = appChatsManager.leave(-peerId); + + if(checkboxes && checked[checkboxes[0].text]) { + promise = promise.then(() => { + return appMessagesManager.flushHistory(peerId); + }) as any; + } onSelect && onSelect(promise); }; diff --git a/src/config/app.ts b/src/config/app.ts index 31ee2203..294f120f 100644 --- a/src/config/app.ts +++ b/src/config/app.ts @@ -13,7 +13,7 @@ const App = { id: 1025907, hash: '452b0359b988148995f22ff0f4229750', version: '0.5.3', - langPackVersion: '0.1.6', + langPackVersion: '0.1.7', langPack: 'macos', langPackCode: 'en', domains: [] as string[], diff --git a/src/lang.ts b/src/lang.ts index c81d710c..c802696b 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -417,6 +417,7 @@ const lang = { "Online": "online", "MessageScheduleSend": "Send Now", "MessageScheduleEditTime": "Reschedule", + "YouLeft": "You left this group", // * macos "AccountSettings.Filters": "Chat Folders", diff --git a/src/lib/appManagers/appChatsManager.ts b/src/lib/appManagers/appChatsManager.ts index c180d8b3..e1a8a76f 100644 --- a/src/lib/appManagers/appChatsManager.ts +++ b/src/lib/appManagers/appChatsManager.ts @@ -44,11 +44,11 @@ export class AppChatsManager { constructor() { rootScope.addMultipleEventsListeners({ - updateChannel: (update) => { + /* updateChannel: (update) => { const channelId = update.channel_id; //console.log('updateChannel:', update); rootScope.broadcast('channel_settings', {channelId}); - }, + }, */ updateChannelParticipant: (update) => { apiManagerProxy.clearCache('channels.getParticipants', (params) => { @@ -268,7 +268,7 @@ export class AppChatsManager { return rights; } - public hasRights(id: number, action: ChatRights, rights?: ChatAdminRights | ChatBannedRights) { + public hasRights(id: number, action: ChatRights, rights?: ChatAdminRights | ChatBannedRights, isThread?: boolean) { const chat: Chat = this.getChat(id); if(chat._ === 'chatEmpty') return false; @@ -285,14 +285,16 @@ export class AppChatsManager { if(!rights) { rights = chat.admin_rights || (chat as Chat.channel).banned_rights || chat.default_banned_rights; - } - - if(!rights) { - return false; + + if(!rights) { + return false; + } } let myFlags: Partial<{[flag in keyof ChatBannedRights['pFlags'] | keyof ChatAdminRights['pFlags']]: true}> = {}; - if(rights) myFlags = rights.pFlags as any; + if(rights) { + myFlags = rights.pFlags as any; + } switch(action) { case 'embed_links': @@ -303,12 +305,16 @@ export class AppChatsManager { case 'send_messages': case 'send_polls': case 'send_stickers': { + if(!isThread && chat.pFlags.left) { + return false; + } + if(rights._ === 'chatBannedRights' && myFlags[action]) { return false; } if(chat._ === 'channel') { - if((!chat.pFlags.megagroup && !myFlags.post_messages)) { + if(!chat.pFlags.megagroup && !myFlags.post_messages) { return false; } } @@ -375,7 +381,6 @@ export class AppChatsManager { } */ public isChannel(id: number) { - if(id < 0) id = -id; const chat = this.chats[id]; return chat && (chat._ === 'channel' || chat._ === 'channelForbidden')/* || this.channelAccess[id] */; } @@ -409,7 +414,6 @@ export class AppChatsManager { } public getChannelInput(id: number): InputChannel { - if(id < 0) id = -id; const chat: Chat = this.getChat(id); if(chat._ === 'chatEmpty' || !(chat as Chat.channel).access_hash) { return { diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 121e1b0d..dbb1e995 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -183,7 +183,7 @@ export class AppMessagesManager { }} = {}; private reloadConversationsPromise: Promise; - private reloadConversationsPeers: number[] = []; + private reloadConversationsPeers: Set = new Set(); public log = logger('MESSAGES', LogTypes.Error | LogTypes.Debug | LogTypes.Log | LogTypes.Warn); @@ -1883,8 +1883,8 @@ export class AppMessagesManager { public reloadConversation(peerId: number | number[]) { [].concat(peerId).forEach(peerId => { - if(!this.reloadConversationsPeers.includes(peerId)) { - this.reloadConversationsPeers.push(peerId); + if(!this.reloadConversationsPeers.has(peerId)) { + this.reloadConversationsPeers.add(peerId); //this.log('will reloadConversation', peerId); } }); @@ -1892,8 +1892,8 @@ export class AppMessagesManager { if(this.reloadConversationsPromise) return this.reloadConversationsPromise; return this.reloadConversationsPromise = new Promise((resolve, reject) => { setTimeout(() => { - const peers = this.reloadConversationsPeers.map(peerId => appPeersManager.getInputDialogPeerById(peerId)); - this.reloadConversationsPeers.length = 0; + const peers = Array.from(this.reloadConversationsPeers).map(peerId => appPeersManager.getInputDialogPeerById(peerId)); + this.reloadConversationsPeers.clear(); apiManager.invokeApi('messages.getPeerDialogs', {peers}).then((result) => { this.dialogsStorage.applyDialogs(result); @@ -1947,7 +1947,7 @@ export class AppMessagesManager { _: 'updateChannelAvailableMessages', channel_id: channelId, available_min_id: maxId - } + } as Update.updateChannelAvailableMessages }); return true; @@ -2297,6 +2297,7 @@ export class AppMessagesManager { if(message.action) { let migrateFrom: number; let migrateTo: number; + const suffix = message.fromId === appUsersManager.getSelf().id ? 'You' : ''; switch(message.action._) { //case 'messageActionChannelEditPhoto': case 'messageActionChatEditPhoto': @@ -2332,7 +2333,6 @@ export class AppMessagesManager { if(message.action.users.length === 1) { message.action.user_id = message.action.users[0]; if(message.fromId === message.action.user_id) { - let suffix = message.fromId === appUsersManager.getSelf().id ? 'You' : ''; if(isChannel) { message.action._ = 'messageActionChatJoined' + suffix; } else { @@ -2346,7 +2346,7 @@ export class AppMessagesManager { case 'messageActionChatDeleteUser': if(message.fromId === message.action.user_id) { - message.action._ = 'messageActionChatLeave'; + message.action._ = 'messageActionChatLeave' + suffix; } break; @@ -4029,19 +4029,19 @@ export class AppMessagesManager { private onUpdateChannel = (update: Update.updateChannel) => { const channelId: number = update.channel_id; const peerId = -channelId; - const channel = appChatsManager.getChat(channelId); - - const needDialog = channel._ === 'channel' && appChatsManager.isInChat(channelId); - const dialog = this.getDialogOnly(peerId); + const channel: Chat.channel = appChatsManager.getChat(channelId); - const canViewHistory = channel._ === 'channel' && (channel.username || !channel.pFlags.left && !channel.pFlags.kicked); + const needDialog = appChatsManager.isInChat(channelId); + + const canViewHistory = !!channel.username || !channel.pFlags.left; const hasHistory = this.historiesStorage[peerId] !== undefined; - + if(canViewHistory !== hasHistory) { delete this.historiesStorage[peerId]; rootScope.broadcast('history_forbidden', peerId); } - + + const dialog = this.getDialogOnly(peerId); if(!!dialog !== needDialog) { if(needDialog) { this.reloadConversation(-channelId); @@ -4299,10 +4299,10 @@ export class AppMessagesManager { }, settings); } - public canWriteToPeer(peerId: number) { + public canWriteToPeer(peerId: number, threadId?: number) { if(peerId < 0) { const isChannel = appPeersManager.isChannel(peerId); - const hasRights = isChannel && appChatsManager.hasRights(-peerId, 'send_messages'); + const hasRights = isChannel && appChatsManager.hasRights(-peerId, 'send_messages', undefined, !!threadId); return !isChannel || hasRights; } else { return appUsersManager.canSendToUser(peerId); diff --git a/src/lib/appManagers/appStateManager.ts b/src/lib/appManagers/appStateManager.ts index 5b0614ed..10f4f5c8 100644 --- a/src/lib/appManagers/appStateManager.ts +++ b/src/lib/appManagers/appStateManager.ts @@ -369,7 +369,7 @@ export class AppStateManager extends EventListenerBase<{ public keepPeerSingle(peerId: number, type: string) { const existsPeerId = this.singlePeerMap.get(type); - if(existsPeerId && existsPeerId !== peerId) { + if(existsPeerId && existsPeerId !== peerId && this.neededPeers.has(existsPeerId)) { const set = this.neededPeers.get(existsPeerId); set.delete(type); diff --git a/src/lib/langPack.ts b/src/lib/langPack.ts index 546c8e7e..a71e5037 100644 --- a/src/lib/langPack.ts +++ b/src/lib/langPack.ts @@ -28,6 +28,7 @@ export const langPack: {[actionType: string]: LangPackKey} = { "messageActionChatAddUser": "ActionAddUser", "messageActionChatAddUsers": "ActionAddUser", "messageActionChatLeave": "ActionLeftUser", + "messageActionChatLeaveYou": "YouLeft", "messageActionChatDeleteUser": "ActionKickUser", "messageActionChatJoinedByLink": "ActionInviteUser", "messageActionPinMessage": "ActionPinnedNoText", diff --git a/src/lib/rootScope.ts b/src/lib/rootScope.ts index 751ac112..81d6816f 100644 --- a/src/lib/rootScope.ts +++ b/src/lib/rootScope.ts @@ -83,7 +83,7 @@ export type BroadcastEvents = { 'chat_full_update': number, 'poll_update': {poll: Poll, results: PollResults}, 'chat_update': number, - 'channel_settings': {channelId: number}, + //'channel_settings': {channelId: number}, 'webpage_updated': {id: string, msgs: number[]}, 'download_progress': any, diff --git a/src/scss/partials/_sidebar.scss b/src/scss/partials/_sidebar.scss index 87d724e4..38fe506f 100644 --- a/src/scss/partials/_sidebar.scss +++ b/src/scss/partials/_sidebar.scss @@ -11,13 +11,13 @@ align-items: center; justify-content: space-between; padding: 0 1rem; - min-height: 56px; + min-height: 3.5rem; flex: 0 0 auto; user-select: none; cursor: default; @include respond-to(handhelds) { - padding: 7.5px 8px; + padding: 0 .5rem; } /* //position: sticky !important; @@ -28,8 +28,8 @@ &__title { flex: 1; font-weight: 500; - padding-left: 24px; - font-size: 20px; + padding-left: 1.5rem; + font-size: 1.25rem; color: var(--primary-text-color); } @@ -40,8 +40,8 @@ &-close-button { overflow: inherit !important; - width: 40px; - height: 40px; + width: 2.5rem; + height: 2.5rem; } &-content { diff --git a/src/scss/partials/popups/_popup.scss b/src/scss/partials/popups/_popup.scss index 7957566d..7522a0bf 100644 --- a/src/scss/partials/popups/_popup.scss +++ b/src/scss/partials/popups/_popup.scss @@ -14,7 +14,7 @@ z-index: 3; background-color: rgba(0, 0, 0, .3); margin: 0; - padding: 2.5rem; + padding: 1.875rem; box-shadow: none; opacity: 0; visibility: hidden; @@ -70,21 +70,9 @@ } &-close { - cursor: pointer; - color: var(--secondary-text-color); z-index: 3; - text-align: center; justify-self: center; - line-height: 1; - //transition: color .2s; - - body.animation-level-0 & { - transition: none; - } - - /* @include hover() { - color: #000; - } */ + flex: 0 0 auto; } &-header {