From 5f00fb9a872c512757736c1487e90cb3434a5d6c Mon Sep 17 00:00:00 2001 From: Eduard Kuzmenko Date: Fri, 19 Mar 2021 16:14:42 +0400 Subject: [PATCH] User permissions Fix forward layout --- src/components/appNavigationController.ts | 2 + src/components/appSearch.ts | 2 +- src/components/appSelectPeers.ts | 104 +++++- src/components/popups/peer.ts | 2 +- src/components/popups/pickUser.ts | 7 +- src/components/ripple.ts | 22 +- .../sidebarLeft/tabs/blockedUsers.ts | 4 +- src/components/sidebarLeft/tabs/contacts.ts | 2 +- src/components/sidebarLeft/tabs/editFolder.ts | 2 +- src/components/sidebarRight/tabs/editGroup.ts | 5 +- .../sidebarRight/tabs/groupPermissions.ts | 340 ++++++++++++++---- .../sidebarRight/tabs/pollResults.ts | 2 +- .../sidebarRight/tabs/userPermissions.ts | 95 +++++ src/helpers/listLoader.ts | 28 ++ src/index.hbs | 2 +- src/lib/appManagers/appChatsManager.ts | 79 +++- src/lib/appManagers/appDialogsManager.ts | 46 ++- src/lib/mtproto/mtprotoworker.ts | 80 +++++ src/scss/partials/_chatlist.scss | 237 ++++++------ src/scss/partials/_checkbox.scss | 5 +- src/scss/partials/_leftSidebar.scss | 61 +--- src/scss/partials/_rightSidebar.scss | 45 ++- src/scss/partials/_selector.scss | 37 +- src/scss/partials/popups/_forward.scss | 19 +- 24 files changed, 880 insertions(+), 348 deletions(-) create mode 100644 src/components/sidebarRight/tabs/userPermissions.ts create mode 100644 src/helpers/listLoader.ts diff --git a/src/components/appNavigationController.ts b/src/components/appNavigationController.ts index 4b2f2b31..c5989ef3 100644 --- a/src/components/appNavigationController.ts +++ b/src/components/appNavigationController.ts @@ -91,6 +91,8 @@ export class AppNavigationController { }, options); } + history.scrollRestoration = 'manual'; + this.pushState(); // * push init state } diff --git a/src/components/appSearch.ts b/src/components/appSearch.ts index 11bae393..4cedbb90 100644 --- a/src/components/appSearch.ts +++ b/src/components/appSearch.ts @@ -9,7 +9,7 @@ export class SearchGroup { list: HTMLUListElement; constructor(public name: string, public type: string, private clearable = true, className?: string, clickable = true, public autonomous = true, public onFound?: () => void) { - this.list = document.createElement('ul'); + this.list = appDialogsManager.createChatList(); this.container = document.createElement('div'); if(className) this.container.className = className; diff --git a/src/components/appSelectPeers.ts b/src/components/appSelectPeers.ts index a877317a..9a8737d3 100644 --- a/src/components/appSelectPeers.ts +++ b/src/components/appSelectPeers.ts @@ -9,15 +9,19 @@ import { cancelEvent, findUpAttribute, findUpClassName } from "../helpers/dom"; import Scrollable from "./scrollable"; import { FocusDirection } from "../helpers/fastSmoothScroll"; import CheckboxField from "./checkboxField"; +import appProfileManager from "../lib/appManagers/appProfileManager"; -type PeerType = 'contacts' | 'dialogs'; +type PeerType = 'contacts' | 'dialogs' | 'channelParticipants'; // TODO: правильная сортировка для addMembers, т.е. для peerType: 'contacts', потому что там идут сначала контакты - потом неконтакты, а должно всё сортироваться по имени let loadedAllDialogs = false, loadAllDialogsPromise: Promise; export default class AppSelectPeers { public container = document.createElement('div'); - public list = document.createElement('ul'); + public list = appDialogsManager.createChatList(/* { + handheldsSize: 66, + avatarSize: 48 + } */); public chatsContainer = document.createElement('div'); public scrollable: Scrollable; public selectedScrollable: Scrollable; @@ -37,7 +41,7 @@ export default class AppSelectPeers { private query = ''; private cachedContacts: number[]; - private loadedWhat: Partial<{[k in 'dialogs' | 'archived' | 'contacts']: true}> = {}; + private loadedWhat: Partial<{[k in 'dialogs' | 'archived' | 'contacts' | 'channelParticipants']: true}> = {}; private renderedPeerIds: Set = new Set(); @@ -48,16 +52,22 @@ export default class AppSelectPeers { private chatRightsAction: ChatRights; private multiSelect = true; private rippleEnabled = true; + private avatarSize = 48; + + private tempIds: {[k in keyof AppSelectPeers['loadedWhat']]: number} = {}; + private peerId = 0; constructor(options: { appendTo: AppSelectPeers['appendTo'], onChange?: AppSelectPeers['onChange'], peerType?: AppSelectPeers['peerType'], + peerId?: number, onFirstRender?: () => void, renderResultsFunc?: AppSelectPeers['renderResultsFunc'], chatRightsAction?: AppSelectPeers['chatRightsAction'], multiSelect?: AppSelectPeers['multiSelect'], - rippleEnabled?: boolean + rippleEnabled?: boolean, + avatarSize?: AppSelectPeers['avatarSize'], }) { for(let i in options) { // @ts-ignore @@ -66,8 +76,19 @@ export default class AppSelectPeers { this.container.classList.add('selector'); + this.peerType.forEach(type => { + this.tempIds[type] = 0; + }); + + let needSwitchList = false; const f = (this.renderResultsFunc || this.renderResults).bind(this); this.renderResultsFunc = (peerIds: number[]) => { + if(needSwitchList) { + this.scrollable.splitUp.replaceWith(this.list); + this.scrollable.setVirtualContainer(this.list); + needSwitchList = false; + } + peerIds = peerIds.filter(peerId => { const notRendered = !this.renderedPeerIds.has(peerId); if(notRendered) this.renderedPeerIds.add(peerId); @@ -149,21 +170,26 @@ export default class AppSelectPeers { const value = this.input.value; if(this.query !== value) { if(this.peerType.includes('contacts')) { - delete this.loadedWhat.contacts; this.cachedContacts = null; } //if(this.peerType.includes('dialogs')) { - delete this.loadedWhat.dialogs; - delete this.loadedWhat.archived; this.folderId = 0; this.offsetIndex = 0; //} + for(let i in this.tempIds) { + // @ts-ignore + ++this.tempIds[i]; + } + + this.list = appDialogsManager.createChatList(); + this.promise = null; - this.list.innerHTML = ''; + this.loadedWhat = {}; this.query = value; this.renderedPeerIds.clear(); + needSwitchList = true; //console.log('selectPeers input:', this.query); this.getMoreResults(); @@ -204,10 +230,16 @@ export default class AppSelectPeers { // в десктопе - сначала без группы, потом архивные, потом контакты без сообщений const pageCount = appPhotosManager.windowH / 72 * 1.25 | 0; + const tempId = ++this.tempIds.dialogs; + this.promise = appMessagesManager.getConversations(this.query, this.offsetIndex, pageCount, this.folderId); const value = await this.promise; this.promise = null; + if(this.tempIds.dialogs !== tempId) { + return; + } + let dialogs = value.dialogs as Dialog[]; if(dialogs.length) { const newOffsetIndex = dialogs[dialogs.length - 1].index || 0; @@ -260,8 +292,13 @@ export default class AppSelectPeers { this.promise = Promise.all(promises); this.cachedContacts = (await this.promise)[0].slice(); */ + const tempId = ++this.tempIds.contacts; this.promise = appUsersManager.getContacts(this.query); this.cachedContacts = (await this.promise).slice(); + if(this.tempIds.contacts !== tempId) { + return; + } + this.cachedContacts.findAndSplice(userId => userId === rootScope.myId); // no my account this.promise = null; } @@ -282,6 +319,31 @@ export default class AppSelectPeers { } } + private async getMoreChannelParticipants() { + if(this.promise) return this.promise; + + if(this.loadedWhat.channelParticipants) { + return; + } + + const pageCount = 50; // same as in group permissions to use cache + + const tempId = ++this.tempIds.channelParticipants; + const promise = appProfileManager.getChannelParticipants(-this.peerId, {_: 'channelParticipantsSearch', q: this.query}, pageCount, this.list.childElementCount); + const participants = await promise; + if(this.tempIds.channelParticipants !== tempId) { + return; + } + + const userIds = participants.participants.map(participant => participant.user_id); + userIds.findAndSplice(u => u === rootScope.myId); + this.renderResultsFunc(userIds); + + if(this.list.childElementCount >= participants.count || participants.participants.length < pageCount) { + this.loadedWhat.channelParticipants = true; + } + } + checkForTriggers = () => { this.scrollable.checkForTriggers(); }; @@ -290,13 +352,15 @@ export default class AppSelectPeers { const get = () => { const promises: Promise[] = []; - if(!loadedAllDialogs && !loadAllDialogsPromise) { - loadAllDialogsPromise = appMessagesManager.getConversationsAll() - .then(() => { - loadedAllDialogs = true; - }, () => { - loadAllDialogsPromise = null; - }); + if(!loadedAllDialogs && (this.peerType.includes('dialogs') || this.peerType.includes('contacts'))) { + if(!loadAllDialogsPromise) { + loadAllDialogsPromise = appMessagesManager.getConversationsAll() + .then(() => { + loadedAllDialogs = true; + }).finally(() => { + loadAllDialogsPromise = null; + }); + } promises.push(loadAllDialogsPromise); } @@ -312,6 +376,10 @@ export default class AppSelectPeers { if(this.peerType.includes('contacts') && !this.loadedWhat.contacts) { promises.push(this.getMoreContacts()); } + + if(this.peerType.includes('channelParticipants') && !this.loadedWhat.channelParticipants) { + promises.push(this.getMoreChannelParticipants()); + } return promises; }; @@ -340,8 +408,8 @@ export default class AppSelectPeers { dialog: peerId, container: this.scrollable, drawStatus: false, - rippleEnabled: true, - avatarSize: 48 + rippleEnabled: this.rippleEnabled, + avatarSize: this.avatarSize }); if(this.multiSelect) { @@ -449,4 +517,4 @@ export default class AppSelectPeers { this.selectedScrollable.scrollIntoViewNew(this.input, 'center', undefined, undefined, FocusDirection.Static); }); } -} \ No newline at end of file +} diff --git a/src/components/popups/peer.ts b/src/components/popups/peer.ts index 9c0fb3d2..c6606832 100644 --- a/src/components/popups/peer.ts +++ b/src/components/popups/peer.ts @@ -8,7 +8,7 @@ export default class PopupPeer extends PopupElement { description: string, buttons: Array }> = {}) { - super('popup-peer' + (className ? ' ' + className : ''), options.buttons); + super('popup-peer' + (className ? ' ' + className : ''), options.buttons, {overlayClosable: true}); let avatarEl = new AvatarElement(); avatarEl.setAttribute('dialog', '1'); diff --git a/src/components/popups/pickUser.ts b/src/components/popups/pickUser.ts index 80f40759..e7edad2e 100644 --- a/src/components/popups/pickUser.ts +++ b/src/components/popups/pickUser.ts @@ -10,7 +10,8 @@ export default class PopupPickUser extends PopupElement { onSelect?: (peerId: number) => Promise | void, onClose?: () => void, placeholder: string, - chatRightsAction?: AppSelectPeers['chatRightsAction'] + chatRightsAction?: AppSelectPeers['chatRightsAction'], + peerId?: number, }) { super('popup-forward', null, {closable: true, overlayClosable: true, body: true}); @@ -42,7 +43,9 @@ export default class PopupPickUser extends PopupElement { }, chatRightsAction: options.chatRightsAction, multiSelect: false, - rippleEnabled: false + rippleEnabled: false, + avatarSize: 46, + peerId: options.peerId, }); //this.scrollable = new Scrollable(this.body); diff --git a/src/components/ripple.ts b/src/components/ripple.ts index 61653561..4834976f 100644 --- a/src/components/ripple.ts +++ b/src/components/ripple.ts @@ -22,7 +22,7 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise; const drawRipple = (clientX: number, clientY: number) => { const startTime = Date.now(); - const span = document.createElement('span'); + const elem = document.createElement('div'); const clickId = rippleClickId++; @@ -40,18 +40,18 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise span.classList.add('hiding'), Math.max(delay - duration / 2, 0)); + setTimeout(() => elem.classList.add('hiding'), Math.max(delay - duration / 2, 0)); setTimeout(() => { //console.log('ripple elapsedTime total pre-remove:', Date.now() - startTime); - span.remove(); + elem.remove(); if(onEnd) onEnd(clickId); }, delay); } else { - span.classList.add('hiding'); + elem.classList.add('hiding'); setTimeout(() => { //console.log('ripple elapsedTime total pre-remove:', Date.now() - startTime); - span.remove(); + elem.remove(); if(onEnd) onEnd(clickId); }, duration / 2); } @@ -82,7 +82,7 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise { let rect = r.getBoundingClientRect(); - span.classList.add('c-ripple__circle'); + elem.classList.add('c-ripple__circle'); let clickX = clientX - rect.left; let clickY = clientY - rect.top; @@ -106,9 +106,9 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise { @@ -124,7 +124,7 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise Promise { const container = this[key]; - const ul = document.createElement('ul'); + const ul = appDialogsManager.createChatList(); const peers = filter[key].slice(); diff --git a/src/components/sidebarRight/tabs/editGroup.ts b/src/components/sidebarRight/tabs/editGroup.ts index 921c0953..d9adca14 100644 --- a/src/components/sidebarRight/tabs/editGroup.ts +++ b/src/components/sidebarRight/tabs/editGroup.ts @@ -113,7 +113,7 @@ export default class AppEditGroupTab extends SliderSuperTab { }); const setPermissionsLength = () => { - permissionsRow.subtitle.innerHTML = flags.reduce((acc, f) => acc + +appChatsManager.hasRights(this.chatId, f, 0), 0) + '/' + flags.length; + permissionsRow.subtitle.innerHTML = flags.reduce((acc, f) => acc + +appChatsManager.hasRights(this.chatId, f, chat.default_banned_rights), 0) + '/' + flags.length; }; setPermissionsLength(); @@ -180,7 +180,8 @@ export default class AppEditGroupTab extends SliderSuperTab { if(appChatsManager.hasRights(this.chatId, 'change_permissions')) { const showChatHistoryCheckboxField = new CheckboxField({ - text: 'Show chat history for new members' + text: 'Show chat history for new members', + withRipple: true }); if(appChatsManager.isChannel(this.chatId) && !(chatFull as ChatFull.channelFull).pFlags.hidden_prehistory) { diff --git a/src/components/sidebarRight/tabs/groupPermissions.ts b/src/components/sidebarRight/tabs/groupPermissions.ts index 68fc955b..d8c22245 100644 --- a/src/components/sidebarRight/tabs/groupPermissions.ts +++ b/src/components/sidebarRight/tabs/groupPermissions.ts @@ -1,10 +1,116 @@ +import { attachClickEvent, cancelEvent, findUpTag } from "../../../helpers/dom"; import ListenerSetter from "../../../helpers/listenerSetter"; -import { Chat, ChatBannedRights } from "../../../layer"; +import ScrollableLoader from "../../../helpers/listLoader"; +import { ChannelParticipant, Chat, ChatBannedRights, Update } from "../../../layer"; import appChatsManager, { ChatRights } from "../../../lib/appManagers/appChatsManager"; +import appDialogsManager from "../../../lib/appManagers/appDialogsManager"; +import appProfileManager from "../../../lib/appManagers/appProfileManager"; +import appUsersManager from "../../../lib/appManagers/appUsersManager"; +import rootScope from "../../../lib/rootScope"; import CheckboxField from "../../checkboxField"; +import PopupPickUser from "../../popups/pickUser"; import Row from "../../row"; import { SettingSection } from "../../sidebarLeft"; import { SliderSuperTabEventable } from "../../sliderTab"; +import { toast } from "../../toast"; +import AppUserPermissionsTab from "./userPermissions"; + +export class ChatPermissions { + public v: Array<{ + flags: ChatRights[], + text: string, + checkboxField?: CheckboxField + }>; + private toggleWith: Partial<{[chatRight in ChatRights]: ChatRights[]}>; + + constructor(options: { + chatId: number, + listenerSetter: ListenerSetter, + appendTo: HTMLElement, + participant?: ChannelParticipant.channelParticipantBanned + }) { + this.v = [ + {flags: ['send_messages'], text: 'Send Messages'}, + {flags: ['send_media'], text: 'Send Media'}, + {flags: ['send_stickers', 'send_gifs'], text: 'Send Stickers & GIFs'}, + {flags: ['send_polls'], text: 'Send Polls'}, + {flags: ['embed_links'], text: 'Send Links'}, + {flags: ['invite_users'], text: 'Add Users'}, + {flags: ['pin_messages'], text: 'Pin Messages'}, + {flags: ['change_info'], text: 'Change Chat Info'} + ]; + + this.toggleWith = { + 'send_messages': ['send_media', 'send_stickers', 'send_polls', 'embed_links'] + }; + + const chat: Chat.chat = appChatsManager.getChat(options.chatId); + const defaultBannedRights = chat.default_banned_rights; + const rights = options.participant ? appChatsManager.combineParticipantBannedRights(options.chatId, options.participant.banned_rights) : defaultBannedRights; + + for(const info of this.v) { + const mainFlag = info.flags[0]; + info.checkboxField = new CheckboxField({ + text: info.text, + checked: appChatsManager.hasRights(options.chatId, mainFlag, rights), + restriction: true, + withRipple: true + }); + + // @ts-ignore + if(options.participant && defaultBannedRights.pFlags[mainFlag]) { + info.checkboxField.input.disabled = true; + + /* options.listenerSetter.add(info.checkboxField.input, 'change', (e) => { + if(!e.isTrusted) { + return; + } + + cancelEvent(e); + toast('This option is disabled for all members in Group Permissions.'); + info.checkboxField.checked = false; + }); */ + + attachClickEvent(info.checkboxField.label, (e) => { + toast('This option is disabled for all members in Group Permissions.'); + }, {listenerSetter: options.listenerSetter}); + } + + if(this.toggleWith[mainFlag]) { + options.listenerSetter.add(info.checkboxField.input, 'change', () => { + if(!info.checkboxField.checked) { + const other = this.v.filter(i => this.toggleWith[mainFlag].includes(i.flags[0])); + other.forEach(info => { + info.checkboxField.checked = false; + }); + } + }); + } + + options.appendTo.append(info.checkboxField.label); + } + } + + public takeOut() { + const rights: ChatBannedRights = { + _: 'chatBannedRights', + until_date: 0x7FFFFFFF, + pFlags: {} + }; + + for(const info of this.v) { + const banned = !info.checkboxField.checked; + if(banned) { + info.flags.forEach(flag => { + // @ts-ignore + rights.pFlags[flag] = true; + }); + } + } + + return rights; + } +} export default class AppGroupPermissionsTab extends SliderSuperTabEventable { public chatId: number; @@ -13,93 +119,20 @@ export default class AppGroupPermissionsTab extends SliderSuperTabEventable { this.container.classList.add('edit-peer-container', 'group-permissions-container'); this.title.innerHTML = 'Permissions'; - class ChatPermissions { - private v: Array<{ - flags: ChatRights[], - text: string, - checkboxField?: CheckboxField - }>; - private toggleWith: Partial<{[chatRight in ChatRights]: ChatRights[]}>; - - constructor(options: { - chatId: number, - listenerSetter: ListenerSetter, - appendTo: HTMLElement, - userId: number - }) { - this.v = [ - {flags: ['send_messages'], text: 'Send Messages'}, - {flags: ['send_media'], text: 'Send Media'}, - {flags: ['send_stickers', 'send_gifs'], text: 'Send Stickers & GIFs'}, - {flags: ['send_polls'], text: 'Send Polls'}, - {flags: ['embed_links'], text: 'Send Links'}, - {flags: ['invite_users'], text: 'Add Users'}, - {flags: ['pin_messages'], text: 'Pin Messages'}, - {flags: ['change_info'], text: 'Change Chat Info'} - ]; - - this.toggleWith = { - 'send_messages': ['send_media', 'send_stickers', 'send_polls', 'embed_links'] - }; - - for(const info of this.v) { - const mainFlag = info.flags[0]; - info.checkboxField = new CheckboxField({ - text: info.text, - checked: appChatsManager.hasRights(options.chatId, mainFlag, options.userId), - restriction: true - }); - - if(this.toggleWith[mainFlag]) { - options.listenerSetter.add(info.checkboxField.input, 'change', () => { - if(!info.checkboxField.checked) { - const other = this.v.filter(i => this.toggleWith[mainFlag].includes(i.flags[0])); - other.forEach(info => { - info.checkboxField.checked = false; - }); - } - }); - } - - options.appendTo.append(info.checkboxField.label); - } - } - - public takeOut() { - const rights: ChatBannedRights = { - _: 'chatBannedRights', - until_date: 0x7FFFFFFF, - pFlags: {} - }; - - for(const info of this.v) { - const banned = !info.checkboxField.checked; - if(banned) { - info.flags.forEach(flag => { - // @ts-ignore - rights.pFlags[flag] = true; - }); - } - } - - return rights; - } - } - + let chatPermissions: ChatPermissions; { const section = new SettingSection({ name: 'What can members of this group do?', }); - const p = new ChatPermissions({ + chatPermissions = new ChatPermissions({ chatId: this.chatId, listenerSetter: this.listenerSetter, appendTo: section.content, - userId: 0 }); this.eventListener.addEventListener('destroy', () => { - appChatsManager.editChatDefaultBannedRights(this.chatId, p.takeOut()); + appChatsManager.editChatDefaultBannedRights(this.chatId, chatPermissions.takeOut()); }); this.scrollable.append(section.container); @@ -110,6 +143,44 @@ export default class AppGroupPermissionsTab extends SliderSuperTabEventable { name: 'Exceptions' }); + const addExceptionRow = new Row({ + title: 'Add Exception', + subtitle: 'Loading...', + icon: 'adduser', + clickable: () => { + new PopupPickUser({ + peerTypes: ['channelParticipants'], + onSelect: (peerId) => { + setTimeout(() => { + openPermissions(peerId); + }, 0); + }, + placeholder: 'Add Exception...', + peerId: -this.chatId, + }); + } + }); + + const openPermissions = async(peerId: number) => { + let participant: AppUserPermissionsTab['participant']; + try { + participant = await appProfileManager.getChannelParticipant(this.chatId, peerId) as any; + + if(participant._ !== 'channelParticipantBanned') { + participant = undefined; + } + } catch(err) { + toast('User is no longer participant'); + return; + } + + const tab = new AppUserPermissionsTab(this.slider); + tab.participant = participant; + tab.chatId = this.chatId; + tab.userId = peerId; + tab.open(); + }; + const removedUsersRow = new Row({ title: 'Removed Users', subtitle: 'No removed users', @@ -117,13 +188,126 @@ export default class AppGroupPermissionsTab extends SliderSuperTabEventable { clickable: true }); - section.content.append(removedUsersRow.container); + section.content.append(addExceptionRow.container, removedUsersRow.container); const c = section.generateContentElement(); c.classList.add('chatlist-container'); + const list = appDialogsManager.createChatList(); + c.append(list); + + attachClickEvent(list, (e) => { + const target = findUpTag(e.target, 'LI'); + if(!target) return; + + const peerId = +target.dataset.peerId; + openPermissions(peerId); + }, {listenerSetter: this.listenerSetter}); + + const setSubtitle = (li: Element, participant: ChannelParticipant.channelParticipantBanned) => { + const bannedRights = participant.banned_rights;//appChatsManager.combineParticipantBannedRights(this.chatId, participant.banned_rights); + const defaultBannedRights = (appChatsManager.getChat(this.chatId) as Chat.channel).default_banned_rights; + const combinedRights = appChatsManager.combineParticipantBannedRights(this.chatId, bannedRights); + + const cantWhat: string[] = [], canWhat: string[] = []; + chatPermissions.v.forEach(info => { + const mainFlag = info.flags[0]; + // @ts-ignore + if(bannedRights.pFlags[mainFlag] && !defaultBannedRights.pFlags[mainFlag]) { + cantWhat.push(info.text); + // @ts-ignore + } else if(!combinedRights.pFlags[mainFlag]) { + canWhat.push(info.text); + } + }); + + const el = li.querySelector('.user-last-message'); + let str: string; + if(cantWhat.length) { + str = 'Can\'t ' + cantWhat.join(cantWhat.length === 2 ? ' and ' : ', '); + } else if(canWhat.length) { + str = 'Can ' + canWhat.join(canWhat.length === 2 ? ' and ' : ', '); + } + + //const user = appUsersManager.getUser(participant.user_id); + if(str) { + el.innerHTML = str; + } + + el.classList.toggle('hide', !str); + }; + + const add = (participant: ChannelParticipant.channelParticipantBanned, append: boolean) => { + const {dom} = appDialogsManager.addDialogNew({ + dialog: participant.user_id, + container: list, + drawStatus: false, + rippleEnabled: true, + avatarSize: 48, + append + }); + + setSubtitle(dom.listEl, participant); + + //dom.titleSpan.innerHTML = 'Chinaza Akachi'; + //dom.lastMessageSpan.innerHTML = 'Can Add Users and Pin Messages'; + }; + + this.listenerSetter.add(rootScope, 'apiUpdate', (update: Update) => { + if(update._ === 'updateChannelParticipant') { + const needAdd = update.new_participant?._ === 'channelParticipantBanned'; + const li = list.querySelector(`[data-peer-id="${update.user_id}"]`); + if(needAdd) { + if(!li) { + add(update.new_participant as ChannelParticipant.channelParticipantBanned, false); + } else { + setSubtitle(li, update.new_participant as ChannelParticipant.channelParticipantBanned); + } + + if(update.prev_participant?._ !== 'channelParticipantBanned') { + ++exceptionsCount; + } + } else { + if(li) { + li.remove(); + } + + if(update.prev_participant?._ === 'channelParticipantBanned') { + --exceptionsCount; + } + } + + setLength(); + } + }); + + const setLength = () => { + addExceptionRow.subtitle.innerHTML = exceptionsCount ? exceptionsCount + ' exceptions' : 'None'; + }; + + let exceptionsCount = 0; + const LOAD_COUNT = 50; + const loader = new ScrollableLoader({ + scrollable: this.scrollable, + getPromise: () => { + return appProfileManager.getChannelParticipants(this.chatId, {_: 'channelParticipantsBanned', q: ''}, LOAD_COUNT, list.childElementCount).then(res => { + for(const participant of res.participants) { + add(participant as ChannelParticipant.channelParticipantBanned, true); + } + + exceptionsCount = res.count; + setLength(); + + return res.participants.length < LOAD_COUNT || res.count === list.childElementCount; + }); + } + }); this.scrollable.append(section.container); } } + + onOpenAfterTimeout() { + this.scrollable.onScroll(); + } } diff --git a/src/components/sidebarRight/tabs/pollResults.ts b/src/components/sidebarRight/tabs/pollResults.ts index 93108b90..40f59dde 100644 --- a/src/components/sidebarRight/tabs/pollResults.ts +++ b/src/components/sidebarRight/tabs/pollResults.ts @@ -51,7 +51,7 @@ export default class AppPollResultsTab extends SliderSuperTab { answerEl.append(answerTitle, answerPercents); // Humans - const list = document.createElement('ul'); + const list = appDialogsManager.createChatList(); list.classList.add('poll-results-voters'); appDialogsManager.setListClickListener(list, () => { diff --git a/src/components/sidebarRight/tabs/userPermissions.ts b/src/components/sidebarRight/tabs/userPermissions.ts new file mode 100644 index 00000000..e19debbe --- /dev/null +++ b/src/components/sidebarRight/tabs/userPermissions.ts @@ -0,0 +1,95 @@ +import { attachClickEvent } from "../../../helpers/dom"; +import { deepEqual } from "../../../helpers/object"; +import { ChannelParticipant } from "../../../layer"; +import appChatsManager from "../../../lib/appManagers/appChatsManager"; +import appDialogsManager from "../../../lib/appManagers/appDialogsManager"; +import appProfileManager from "../../../lib/appManagers/appProfileManager"; +import appUsersManager from "../../../lib/appManagers/appUsersManager"; +import Button from "../../button"; +import { SettingSection } from "../../sidebarLeft"; +import { SliderSuperTabEventable } from "../../sliderTab"; +import { ChatPermissions } from "./groupPermissions"; + +export default class AppUserPermissionsTab extends SliderSuperTabEventable { + public participant: ChannelParticipant.channelParticipantBanned; + public chatId: number; + public userId: number; + + protected init() { + this.container.classList.add('edit-peer-container', 'user-permissions-container'); + this.title.innerHTML = 'User Permissions'; + + { + const section = new SettingSection({ + name: 'What can this user do?', + }); + + const div = document.createElement('div'); + div.classList.add('chatlist-container'); + section.content.insertBefore(div, section.title); + + const list = appDialogsManager.createChatList(); + div.append(list); + + const {dom} = appDialogsManager.addDialogNew({ + dialog: this.userId, + container: list, + drawStatus: false, + rippleEnabled: true, + avatarSize: 48 + }); + + dom.lastMessageSpan.innerHTML = appUsersManager.getUserStatusString(this.userId); + + const p = new ChatPermissions({ + chatId: this.chatId, + listenerSetter: this.listenerSetter, + appendTo: section.content, + participant: this.participant + }); + + this.eventListener.addEventListener('destroy', () => { + //appChatsManager.editChatDefaultBannedRights(this.chatId, p.takeOut()); + const rights = p.takeOut(); + if(deepEqual(this.participant.banned_rights.pFlags, rights.pFlags)) { + return; + } + + appChatsManager.editBanned(this.chatId, this.participant, rights); + }); + + this.scrollable.append(section.container); + } + + { + const section = new SettingSection({}); + + const btnDelete = Button('btn-primary btn-transparent danger', {icon: 'deleteuser', text: 'Ban and Remove From Group'}); + + attachClickEvent(btnDelete, () => { + /* new PopupPeer('popup-delete-group', { + peerId: -this.chatId, + title: 'Delete Group?', + description: `Are you sure you want to delete this group? All members will be removed, and all messages will be lost.`, + buttons: addCancelButton([{ + text: 'DELETE', + callback: () => { + toggleDisability([btnDelete], true); + + appChatsManager.deleteChannel(this.chatId).then(() => { + this.close(); + }, () => { + toggleDisability([btnDelete], false); + }); + }, + isDanger: true + }]) + }).show(); */ + }, {listenerSetter: this.listenerSetter}); + + section.content.append(btnDelete); + + this.scrollable.append(section.container); + } + } +} diff --git a/src/helpers/listLoader.ts b/src/helpers/listLoader.ts new file mode 100644 index 00000000..139aab0f --- /dev/null +++ b/src/helpers/listLoader.ts @@ -0,0 +1,28 @@ +import Scrollable from "../components/scrollable"; + +export default class ScrollableLoader { + constructor(options: { + scrollable: Scrollable, + getPromise: () => Promise + }) { + let loading = false; + options.scrollable.onScrolledBottom = () => { + if(loading) { + return; + } + + loading = true; + options.getPromise().then(done => { + loading = false; + + if(done) { + options.scrollable.onScrolledBottom = null; + } else { + options.scrollable.checkForTriggers(); + } + }, () => { + loading = false; + }); + }; + } +} diff --git a/src/index.hbs b/src/index.hbs index aaa7f855..3f75e662 100644 --- a/src/index.hbs +++ b/src/index.hbs @@ -121,7 +121,7 @@
-
    +
      diff --git a/src/lib/appManagers/appChatsManager.ts b/src/lib/appManagers/appChatsManager.ts index 2db13a9d..4837ea42 100644 --- a/src/lib/appManagers/appChatsManager.ts +++ b/src/lib/appManagers/appChatsManager.ts @@ -1,7 +1,8 @@ import { MOUNT_CLASS_TO } from "../../config/debug"; import { numberThousandSplitter } from "../../helpers/number"; import { isObject, safeReplaceObject, copy, deepEqual } from "../../helpers/object"; -import { Chat, ChatAdminRights, ChatBannedRights, ChatFull, ChatParticipants, InputChannel, InputChatPhoto, InputFile, InputPeer, SendMessageAction, Update, Updates } from "../../layer"; +import { ChannelParticipant, Chat, ChatAdminRights, ChatBannedRights, ChatFull, ChatParticipant, ChatParticipants, InputChannel, InputChatPhoto, InputFile, InputPeer, SendMessageAction, Update, Updates } from "../../layer"; +import apiManagerProxy from "../mtproto/mtprotoworker"; import apiManager from '../mtproto/mtprotoworker'; import { RichTextProcessor } from "../richtextprocessor"; import rootScope from "../rootScope"; @@ -40,6 +41,13 @@ export class AppChatsManager { break; } + case 'updateChannelParticipant': { + apiManagerProxy.clearCache('channels.getParticipants', (params) => { + return (params.channel as InputChannel.inputChannel).channel_id === update.channel_id; + }); + break; + } + case 'updateChatDefaultBannedRights': { const chatId = -appPeersManager.getPeerId(update.peer); const chat: Chat = this.getChat(chatId); @@ -182,7 +190,22 @@ export class AppChatsManager { return this.chats[id] || {_: 'chatEmpty', id, deleted: true, access_hash: '', pFlags: {}/* this.channelAccess[id] */}; } - public hasRights(id: number, action: ChatRights, userId?: number) { + public combineParticipantBannedRights(id: number, rights: ChatBannedRights) { + const chat: Chat.channel = this.getChat(id); + + if(chat.default_banned_rights) { + rights = copy(rights); + const defaultRights = chat.default_banned_rights.pFlags; + for(let i in defaultRights) { + // @ts-ignore + rights.pFlags[i] = defaultRights[i]; + } + } + + return rights; + } + + public hasRights(id: number, action: ChatRights, rights?: ChatAdminRights | ChatBannedRights) { const chat: Chat = this.getChat(id); if(chat._ === 'chatEmpty') return false; @@ -193,15 +216,14 @@ export class AppChatsManager { return false; } - if(userId !== undefined && appUsersManager.getSelf().id === userId) { - userId = undefined; - } - - if(chat.pFlags.creator && userId === undefined) { + if(chat.pFlags.creator && rights === undefined) { return true; } - const rights = (userId === undefined && (chat.admin_rights || (chat as Chat.channel).banned_rights)) || chat.default_banned_rights; + if(!rights) { + rights = chat.admin_rights || (chat as Chat.channel).banned_rights || chat.default_banned_rights; + } + if(!rights) { return false; } @@ -641,6 +663,47 @@ export class AppChatsManager { rootScope.broadcast('peer_bio_edit', -id); }); } + + public editBanned(id: number, participant: number | ChannelParticipant, banned_rights: ChatBannedRights) { + const userId = typeof(participant) === 'number' ? participant : participant.user_id; + return apiManager.invokeApi('channels.editBanned', { + channel: this.getChannelInput(id), + user_id: appUsersManager.getUserInput(userId), + banned_rights + }).then((updates) => { + this.onChatUpdated(id, updates); + + if(typeof(participant) !== 'number') { + const timestamp = Date.now() / 1000 | 0; + apiUpdatesManager.processUpdateMessage({ + _: 'updateShort', + update: { + _: 'updateChannelParticipant', + channel_id: id, + date: timestamp, + //qts: 0, + user_id: userId, + prev_participant: participant, + new_participant: Object.keys(banned_rights.pFlags).length ? { + _: 'channelParticipantBanned', + date: timestamp, + banned_rights, + kicked_by: appUsersManager.getSelf().id, + user_id: userId, + pFlags: {} + } : undefined + } as Update.updateChannelParticipant + }); + } + }); + } + + public kickFromChannel(id: number, userId: number) { + return this.editBanned(id, userId, { + _: 'chatBannedRights', + until_date: 0 + }); + } } const appChatsManager = new AppChatsManager(); diff --git a/src/lib/appManagers/appDialogsManager.ts b/src/lib/appManagers/appDialogsManager.ts index eb5c6617..3ca0c2f2 100644 --- a/src/lib/appManagers/appDialogsManager.ts +++ b/src/lib/appManagers/appDialogsManager.ts @@ -39,7 +39,7 @@ type DialogDom = { lastTimeSpan: HTMLSpanElement, unreadMessagesSpan: HTMLSpanElement, lastMessageSpan: HTMLSpanElement, - containerEl: HTMLDivElement, + containerEl: HTMLElement, listEl: HTMLLIElement, muteAnimationTimeout?: number }; @@ -222,7 +222,7 @@ export class AppDialogsManager { private lastActiveElements: Set = new Set(); constructor() { - this.chatListArchived = document.createElement('ul'); + this.chatListArchived = this.createChatList(); this.chatListArchived.id = 'dialogs-archived'; this.chatLists = { @@ -686,7 +686,7 @@ export class AppDialogsManager { positionElementByIndex(menuTab, containerToAppend, filter.orderIndex); //containerToAppend.append(li); - const ul = document.createElement('ul'); + const ul = this.createChatList(); const div = document.createElement('div'); div.append(ul); div.dataset.filterId = '' + filter.id; @@ -914,17 +914,15 @@ export class AppDialogsManager { //cancelEvent(e); this.log('dialogs click list'); - let target = e.target as HTMLElement; - let elem = target.classList.contains('rp') ? target : findUpClassName(target, 'rp'); + const target = e.target as HTMLElement; + const elem = findUpTag(target, 'LI'); if(!elem) { return; } - elem = elem.parentElement; - if(autonomous) { - let sameElement = lastActiveListElement === elem; + const sameElement = lastActiveListElement === elem; if(lastActiveListElement && !sameElement) { lastActiveListElement.classList.remove('active'); } @@ -939,8 +937,8 @@ export class AppDialogsManager { if(elem) { if(onFound) onFound(); - let peerId = +elem.dataset.peerId; - let lastMsgId = +elem.dataset.mid || undefined; + const peerId = +elem.dataset.peerId; + const lastMsgId = +elem.dataset.mid || undefined; appImManager.setPeer(peerId, lastMsgId); } else { @@ -963,6 +961,22 @@ export class AppDialogsManager { } } + public createChatList(/* options: { + avatarSize?: number, + handheldsSize?: number, + //size?: number, + } = {} */) { + const list = document.createElement('ul'); + list.classList.add('chatlist'/* , + 'chatlist-avatar-' + (options.avatarSize || 54) *//* , 'chatlist-' + (options.size || 72) */); + + /* if(options.handheldsSize) { + list.classList.add('chatlist-handhelds-' + options.handheldsSize); + } */ + + return list; + } + private reorderDialogs() { //const perf = performance.now(); if(this.reorderDialogsTimeout) { @@ -1291,16 +1305,12 @@ export class AppDialogsManager { //captionDiv.append(titleSpan); //captionDiv.append(span); - const paddingDiv = document.createElement('div'); - paddingDiv.classList.add('rp'); - paddingDiv.append(avatarEl, captionDiv); - + const li = document.createElement('li'); if(rippleEnabled) { - ripple(paddingDiv); + ripple(li); } - const li = document.createElement('li'); - li.append(paddingDiv); + li.append(avatarEl, captionDiv); li.dataset.peerId = '' + peerId; const statusSpan = document.createElement('span'); @@ -1335,7 +1345,7 @@ export class AppDialogsManager { lastTimeSpan, unreadMessagesSpan, lastMessageSpan: span, - containerEl: paddingDiv, + containerEl: li, listEl: li }; diff --git a/src/lib/mtproto/mtprotoworker.ts b/src/lib/mtproto/mtprotoworker.ts index 8ef442a1..e0cf6f89 100644 --- a/src/lib/mtproto/mtprotoworker.ts +++ b/src/lib/mtproto/mtprotoworker.ts @@ -53,6 +53,21 @@ export class ApiManagerProxy extends CryptoWorkerMethods { private hashes: {[method: string]: HashOptions} = {}; + private apiPromisesSingle: { + [q: string]: Promise + } = {}; + private apiPromisesCacheable: { + [method: string]: { + [queryJSON: string]: { + timestamp: number, + promise: Promise, + fulfilled: boolean, + timeout?: number, + params: any + } + } + } = {}; + private isSWRegistered = true; private debug = DEBUG /* && false */; @@ -354,6 +369,71 @@ export class ApiManagerProxy extends CryptoWorkerMethods { }); } + public invokeApiSingle(method: T, params: MethodDeclMap[T]['req'] = {} as any, options: InvokeApiOptions = {}): Promise { + const q = method + '-' + JSON.stringify(params); + if(this.apiPromisesSingle[q]) { + return this.apiPromisesSingle[q]; + } + + return this.apiPromisesSingle[q] = this.invokeApi(method, params, options).finally(() => { + delete this.apiPromisesSingle[q]; + }); + } + + public invokeApiCacheable(method: T, params: MethodDeclMap[T]['req'] = {} as any, options: InvokeApiOptions & Partial<{cacheSeconds: number, override: boolean}> = {}): Promise { + const cache = this.apiPromisesCacheable[method] ?? (this.apiPromisesCacheable[method] = {}); + const queryJSON = JSON.stringify(params); + const item = cache[queryJSON]; + if(item && (!options.override || !item.fulfilled)) { + return item.promise; + } + + if(options.override) { + if(item && item.timeout) { + clearTimeout(item.timeout); + delete item.timeout; + } + + delete options.override; + } + + let timeout: number; + if(options.cacheSeconds) { + timeout = window.setTimeout(() => { + delete cache[queryJSON]; + }, options.cacheSeconds * 1000); + delete options.cacheSeconds; + } + + const promise = this.invokeApi(method, params, options); + + cache[queryJSON] = { + timestamp: Date.now(), + fulfilled: false, + timeout, + promise, + params + }; + + return promise; + } + + public clearCache(method: T, verify: (params: MethodDeclMap[T]['req']) => boolean) { + const cache = this.apiPromisesCacheable[method]; + if(cache) { + for(const queryJSON in cache) { + const item = cache[queryJSON]; + if(verify(item.params)) { + if(item.timeout) { + clearTimeout(item.timeout); + } + + delete cache[queryJSON]; + } + } + } + } + /* private computeHash(smth: any[]) { smth = smth.slice().sort((a, b) => a.id - b.id); //return smth.reduce((hash, v) => (((hash * 0x4F25) & 0x7FFFFFFF) + v.id) & 0x7FFFFFFF, 0); diff --git a/src/scss/partials/_chatlist.scss b/src/scss/partials/_chatlist.scss index c05321d6..bca84739 100644 --- a/src/scss/partials/_chatlist.scss +++ b/src/scss/partials/_chatlist.scss @@ -7,25 +7,124 @@ } } - ul { - margin: 0; - display: flex; - flex-direction: column; - position: relative; + .search-group { width: 100%; + //border-bottom: 1px solid #DADCE0; + padding: 1rem 0 .5rem; + margin-bottom: 17px; - user-select: none; - -webkit-user-select: none; /* disable selection/Copy of UIWebView */ - -webkit-touch-callout: none; /* disable the IOS popup when long-press on a link */ + @include respond-to(handhelds) { + margin-bottom: 0; + } + + &__name { + color: $color-gray; + padding: 0 23px; + padding-bottom: 1rem; + font-weight: 500; + user-select: none; + + @include respond-to(handhelds) { + padding: 5px 9px 0 16px; + font-size: 15px; + } + } + + &-contacts { + border-bottom: 1px solid #dadce0; + + @include respond-to(handhelds) { + padding: 0px 0 2px; + } + + // .search-group__name { + // padding-bottom: 17px; + + // @include respond-to(handhelds) { + // padding-bottom: 0; + // } + // } + } + + &-people.search-group-contacts { + padding: 5px 0 5px !important; + } + + &:last-child { + border-bottom: none; + } } + .search-super { + .search-group { + margin-bottom: 0px; + padding: 4px 0 0; + + &__name { + padding-top: 1rem; + display: flex; + justify-content: space-between; + } + } + } + +} + +ul.chatlist { + padding: 0 .5rem; + + @include respond-to(handhelds) { + padding: 0; + } +} + +.chatlist { + //--avatarSize: 54px; + //--height: 72px; + margin: 0; + display: flex; + flex-direction: column; + position: relative; + width: 100%; + + user-select: none; + -webkit-user-select: none; /* disable selection/Copy of UIWebView */ + -webkit-touch-callout: none; /* disable the IOS popup when long-press on a link */ + + /* &.chatlist-avatar-48 { + --avatarSize: 48px; + } + + @include respond-to(handhelds) { + &.chatlist-handhelds-66 { + --height: 66px; + } + } */ + li { background-color: #fff; + //height: var(--height); + height: 72px; + //max-height: var(--height); + border-radius: $border-radius-medium; + display: flex; + align-items: flex-start; // TODO: проверить разницу в производительности с align-items: center; + flex-direction: row; + position: relative; + cursor: pointer; + padding: 9px 8.5px; + /* padding-top: calc((var(--height) - var(--avatarSize)) / 2); + padding-bottom: calc((var(--height) - var(--avatarSize)) / 2); + padding-right: 8.5px; + padding-left: 8.5px; */ + overflow: hidden; @include respond-to(handhelds) { - padding-bottom: 0px; + border-radius: 0; } + @include hover-background-effect(); + &.is-muted { .user-title { &:after { @@ -87,44 +186,15 @@ margin-left: 0; } } */ - } - li > .rp { - height: 72px; - max-height: 72px; - border-radius: $border-radius-medium; - display: flex; - align-items: flex-start; // TODO: проверить разницу в производительности с align-items: center; - flex-direction: row; - position: relative; - cursor: pointer; - padding: 9px 8.5px; - margin: 0 8px; - overflow: hidden; - - /* html.is-safari & { - margin-right: 3px; - } */ - - @include respond-to(handhelds) { - padding: 9px 12px 9px 9px !important; - border-radius: 0; - margin: 0; - overflow: hidden; - } - - @include hover-background-effect(); - } - - li.menu-open { - > .rp { + &.menu-open { background: var(--color-gray-hover); } - } - @include respond-to(not-handhelds) { - li.active > .rp { - background: var(--color-gray-hover); + @include respond-to(not-handhelds) { + &.active { + background: var(--color-gray-hover); + } } } @@ -261,96 +331,25 @@ li.is-muted .unread { background: #c5c9cc; } - - .search-group { - width: 100%; - //border-bottom: 1px solid #DADCE0; - padding: 1rem 0 .5rem; - margin-bottom: 17px; - - @include respond-to(handhelds) { - margin-bottom: 0; - } - - &__name { - color: $color-gray; - padding: 0 23px; - padding-bottom: 1rem; - font-weight: 500; - user-select: none; - - @include respond-to(handhelds) { - padding: 5px 9px 0 16px; - font-size: 15px; - } - } - - &-contacts { - border-bottom: 1px solid #dadce0; - - @include respond-to(handhelds) { - padding: 0px 0 2px; - } - - // .search-group__name { - // padding-bottom: 17px; - - // @include respond-to(handhelds) { - // padding-bottom: 0; - // } - // } - } - - &-people.search-group-contacts { - padding: 5px 0 5px !important; - } - - &:last-child { - border-bottom: none; - } - } - - .search-super { - .search-group { - margin-bottom: 0px; - padding: 4px 0 0; - - &__name { - padding-top: 1rem; - display: flex; - justify-content: space-between; - } - } - } - } // use together like class="chatlist-container contacts-container" .contacts-container, .search-group-contacts { li { - //margin-bottom: 2px; - padding-bottom: 4px; - padding-top: 2px; + padding: .75rem; @include respond-to(handhelds) { - padding: 0; + height: 66px; + padding-top: 9px; + padding-bottom: 9px; } } - li > .rp { - padding: 9px 11.5px !important; - height: 66px; - - //@include respond-to(handhelds) { - //height: 62px; - //} - } - .user-caption { padding: 1px 3.5px 1px 13px; @include respond-to(handhelds) { - padding: 0px 4px 0px 14px; + padding: 0 4px 0 14px; } } diff --git a/src/scss/partials/_checkbox.scss b/src/scss/partials/_checkbox.scss index de96d0ec..209e862e 100644 --- a/src/scss/partials/_checkbox.scss +++ b/src/scss/partials/_checkbox.scss @@ -95,6 +95,10 @@ .checkbox-ripple { overflow: hidden; border-radius: $border-radius-medium; + + .checkbox-box, .checkbox-caption { + pointer-events: none; + } } .checkbox-field-round { @@ -168,7 +172,6 @@ &::before { border: 2px solid #707579; border-radius: 50%; - background-color: white; opacity: 1; transition: border-color .1s ease, opacity .1s ease; } diff --git a/src/scss/partials/_leftSidebar.scss b/src/scss/partials/_leftSidebar.scss index 4cfc4cd5..1d32b189 100644 --- a/src/scss/partials/_leftSidebar.scss +++ b/src/scss/partials/_leftSidebar.scss @@ -263,7 +263,7 @@ } .search-group-people { - ul { + .chatlist { display: flex; flex-direction: row; padding-left: 4px; @@ -272,13 +272,7 @@ } li { - margin-right: 5px; - padding: 0; - } - - .rp { height: 98px; - max-height: 98px; border-radius: 10px; max-width: 78px; width: 78px; @@ -286,7 +280,8 @@ display: flex; flex-direction: column; padding: 12px 0 0 !important; - margin: 0; + margin: 0 5px 0 0; + flex: 0 0 auto; @include respond-to(handhelds) { width: 77px; @@ -294,7 +289,7 @@ } } - .dialog-title-details { + .dialog-title-details, .dialog-subtitle { display: none; } @@ -628,16 +623,8 @@ .folder-list { li { - padding-bottom: 2px; - - .rp { - padding: 8px 11px !important; - height: 48px !important; - - @include respond-to(handhelds) { - padding: 8px 12px !important; - } - } + padding: 9px 11px; + height: 50px; } .user-caption { @@ -680,16 +667,10 @@ .popup-forward, .included-chatlist-container { .selector { - ul { - li > .rp { - margin: 0 .5rem; + .chatlist { + li { padding: 7px .75rem !important; height: 3.75rem; - max-height: 3.75rem; - - @include respond-to(handhelds) { - margin: 0; - } } .user-caption { @@ -705,13 +686,6 @@ } } -.popup-forward { - li > .rp { - height: 3.875rem !important; - max-height: 3.875rem !important; - } -} - .included-chatlist-container { .sidebar-left-h2 { padding: 6px 24px 8px 24px; @@ -776,7 +750,9 @@ @include respond-to(handhelds) { li { - padding-top: 0; + height: 62px; + padding-top: 7px; + padding-bottom: 7px; } .user-caption { @@ -791,16 +767,12 @@ --size: 46px; --multiplier: 1.173913; } - - li > .rp { - height: 62px; - } } } @include respond-to(handhelds) { .search-group-recent.search-group.search-group-contacts ul { - margin-top: -2px; + margin-top: 0; } .search-group.search-group-contacts ul, .search-group.search-group-messages ul { @@ -1045,9 +1017,12 @@ } .blocked-users-container { - li > .rp { + li { height: 66px; - max-height: 66px; + padding-top: 9px; + padding-bottom: 9px; + + border-radius: $border-radius-medium; } .user-caption { @@ -1061,7 +1036,7 @@ ul { margin-top: .3125rem; - padding: 0 3px; + padding: 0 .6875rem; } } diff --git a/src/scss/partials/_rightSidebar.scss b/src/scss/partials/_rightSidebar.scss index 5e555218..403016bf 100644 --- a/src/scss/partials/_rightSidebar.scss +++ b/src/scss/partials/_rightSidebar.scss @@ -696,7 +696,6 @@ color: #707579; padding: 0 16px 8px 16px; margin: 0; - padding-bottom: 8px; font-weight: 500; justify-content: space-between; display: flex; @@ -753,15 +752,11 @@ } li { - padding-bottom: 2px; + height: 50px; + padding: 9px; - > .rp { - padding: 8px 5px; - height: 48px; - - @include respond-to(not-handhelds) { - padding: 8px 12px; - } + @include respond-to(not-handhelds) { + padding: 9px 12px; } } } @@ -789,8 +784,35 @@ } } - .checkbox-field { - margin: 0 1.1875rem; + // * supernew and correct layout + .chatlist { + padding: 0; + + li { + height: 72px; + padding: 0 .75rem; + align-items: center; + } + + .user-caption { + padding-left: .75rem; + } + + p { + height: auto; + } + + span { + line-height: 1.3125; + } + + .dialog-subtitle { + margin-top: .125rem; + } + + .user-last-message { + font-size: .875rem; + } } } @@ -817,7 +839,6 @@ .group-type-container { .sidebar-left-section-caption { font-size: .875rem; - line-height: 1rem; margin-top: .8125rem; } diff --git a/src/scss/partials/_selector.scss b/src/scss/partials/_selector.scss index 740988df..2057f070 100644 --- a/src/scss/partials/_selector.scss +++ b/src/scss/partials/_selector.scss @@ -125,9 +125,21 @@ } } - ul { + .chatlist { + li { + padding-top: .75rem; + padding-bottom: .75rem; + + @include respond-to(handhelds) { + height: 66px; + padding-top: 9px; + padding-bottom: 9px; + } + } + .user-caption { - padding: 1px 3.5px 1px 12px; + padding-left: .75rem; + padding-right: 0; } p { @@ -137,25 +149,6 @@ span.user-last-message { font-size: 14px; } - - li { - padding-bottom: 0; - - > .rp { - margin: 0px 9px 0px 8px; - padding: 12px 8.5px; - - @include respond-to(handhelds) { - height: 66px; - max-height: 66px; - margin: 0; - } - - /* html.is-safari & { - margin-right: 4px; - } */ - } - } } hr { @@ -194,4 +187,4 @@ --offset: 6px; } } -} \ No newline at end of file +} diff --git a/src/scss/partials/popups/_forward.scss b/src/scss/partials/popups/_forward.scss index de85ba6a..e57a7d8b 100644 --- a/src/scss/partials/popups/_forward.scss +++ b/src/scss/partials/popups/_forward.scss @@ -6,14 +6,14 @@ width: 420px; max-width: 420px; //padding: 12px 20px 32.5px; - padding: 9px 0 0 0; + padding: 7px 0 0 0; max-height: unquote('min(40.625rem, 100%)'); height: 40.625rem; } &-header { flex: 0 0 auto; - margin-bottom: 4px; + margin-bottom: 3px; padding: 0 1rem; } @@ -35,10 +35,17 @@ font-size: 1.25rem; padding: .5rem 1.5rem; width: 100%; + line-height: 1.3125; } - /* ul li > .rp { - margin-left: 0; - } */ + .chatlist { + margin-top: 0 !important; + + li { + height: 3.875rem !important; + padding-top: .5rem !important; + padding-bottom: .5rem !important; + } + } } -} \ No newline at end of file +}