Browse Source

Blocked users almost finished

master
Eduard Kuzmenko 4 years ago
parent
commit
64f2a64351
  1. 4
      src/components/appSelectPeers.ts
  2. 52
      src/components/popups/forward.ts
  3. 53
      src/components/popups/pickUser.ts
  4. 115
      src/components/sidebarLeft/tabs/blockedUsers.ts
  5. 20
      src/components/sidebarLeft/tabs/privacyAndSecurity.ts
  6. 8
      src/components/sliderTab.ts
  7. 53
      src/helpers/eventListenerBase.ts
  8. 20
      src/helpers/listenerSetter.ts
  9. 26
      src/lib/appManagers/appPeersManager.ts
  10. 42
      src/lib/appManagers/appUsersManager.ts
  11. 5
      src/lib/rootScope.ts
  12. 6
      src/scss/partials/_leftSidebar.scss

4
src/components/appSelectPeers.ts

@ -44,8 +44,8 @@ export default class AppSelectPeers {
private appendTo: HTMLElement; private appendTo: HTMLElement;
private onChange: (length: number) => void; private onChange: (length: number) => void;
private peerType: PeerType[] = ['dialogs']; private peerType: PeerType[] = ['dialogs'];
private renderResultsFunc?: (peerIds: number[]) => void; private renderResultsFunc: (peerIds: number[]) => void;
private chatRightsAction?: ChatRights; private chatRightsAction: ChatRights;
private multiSelect = true; private multiSelect = true;
private rippleEnabled = true; private rippleEnabled = true;

52
src/components/popups/forward.ts

@ -1,48 +1,24 @@
import { isTouchSupported } from "../../helpers/touchSupport";
import appImManager from "../../lib/appManagers/appImManager"; import appImManager from "../../lib/appManagers/appImManager";
import AppSelectPeers from "../appSelectPeers"; import PopupPickUser from "./pickUser";
import PopupElement from ".";
export default class PopupForward extends PopupElement {
private selector: AppSelectPeers;
//private scrollable: Scrollable;
export default class PopupForward extends PopupPickUser {
constructor(fromPeerId: number, mids: number[], onSelect?: () => Promise<void> | void, onClose?: () => void) { constructor(fromPeerId: number, mids: number[], onSelect?: () => Promise<void> | void, onClose?: () => void) {
super('popup-forward', null, {closable: true, overlayClosable: true, body: true}); super({
peerTypes: ['dialogs', 'contacts'],
if(onClose) this.onClose = onClose; onSelect: async(peerId) => {
if(onSelect) {
this.selector = new AppSelectPeers({ const res = onSelect();
appendTo: this.body, if(res instanceof Promise) {
onChange: async() => { await res;
const peerId = this.selector.getSelected()[0]; }
this.btnClose.click(); }
this.selector = null;
await (onSelect ? onSelect() || Promise.resolve() : Promise.resolve());
appImManager.setInnerPeer(peerId); appImManager.setInnerPeer(peerId);
appImManager.chat.input.initMessagesForward(fromPeerId, mids.slice()); appImManager.chat.input.initMessagesForward(fromPeerId, mids.slice());
}, },
peerType: ['dialogs', 'contacts'], onClose,
onFirstRender: () => { placeholder: 'Forward to...',
this.show(); chatRightsAction: 'send'
this.selector.checkForTriggers(); // ! due to zero height before mounting
if(!isTouchSupported) {
this.selector.input.focus();
}
},
chatRightsAction: 'send',
multiSelect: false,
rippleEnabled: false
}); });
//this.scrollable = new Scrollable(this.body);
this.selector.input.placeholder = 'Forward to...';
this.title.append(this.selector.input);
} }
} }

53
src/components/popups/pickUser.ts

@ -0,0 +1,53 @@
import { isTouchSupported } from "../../helpers/touchSupport";
import AppSelectPeers from "../appSelectPeers";
import PopupElement from ".";
export default class PopupPickUser extends PopupElement {
protected selector: AppSelectPeers;
constructor(options: {
peerTypes: AppSelectPeers['peerType'],
onSelect?: (peerId: number) => Promise<void> | void,
onClose?: () => void,
placeholder: string,
chatRightsAction?: AppSelectPeers['chatRightsAction']
}) {
super('popup-forward', null, {closable: true, overlayClosable: true, body: true});
if(options.onClose) this.onClose = options.onClose;
this.selector = new AppSelectPeers({
appendTo: this.body,
onChange: async() => {
const peerId = this.selector.getSelected()[0];
this.btnClose.click();
this.selector = null;
if(options.onSelect) {
const res = options.onSelect(peerId);
if(res instanceof Promise) {
await res;
}
}
},
peerType: options.peerTypes,
onFirstRender: () => {
this.show();
this.selector.checkForTriggers(); // ! due to zero height before mounting
if(!isTouchSupported) {
this.selector.input.focus();
}
},
chatRightsAction: options.chatRightsAction,
multiSelect: false,
rippleEnabled: false
});
//this.scrollable = new Scrollable(this.body);
this.selector.input.placeholder = options.placeholder;
this.title.append(this.selector.input);
}
}

115
src/components/sidebarLeft/tabs/blockedUsers.ts

@ -0,0 +1,115 @@
import { SliderSuperTab } from "../../slider";
import { SettingSection } from "..";
import { attachContextMenuListener, openBtnMenu, positionMenu } from "../../misc";
import { attachClickEvent, findUpTag } from "../../../helpers/dom";
import ButtonMenu from "../../buttonMenu";
import appDialogsManager from "../../../lib/appManagers/appDialogsManager";
import appUsersManager from "../../../lib/appManagers/appUsersManager";
import Button from "../../button";
import PopupPickUser from "../../popups/pickUser";
import rootScope from "../../../lib/rootScope";
export default class AppBlockedUsersTab extends SliderSuperTab {
public peerIds: number[];
private menuElement: HTMLElement;
protected init() {
this.container.classList.add('blocked-users-container');
this.title.innerText = 'Blocked Users';
{
const section = new SettingSection({
caption: 'Blocked users will not be able to contact you and will not see your Last Seen time.'
});
this.scrollable.append(section.container);
}
const btnAdd = Button('btn-circle btn-corner tgico-add is-visible');
this.content.append(btnAdd);
attachClickEvent(btnAdd, (e) => {
new PopupPickUser({
peerTypes: ['contacts'],
placeholder: 'Block user...',
onSelect: (peerId) => {
//console.log('block', peerId);
appUsersManager.toggleBlock(peerId, true);
},
});
}, {listenerSetter: this.listenerSetter});
const list = document.createElement('ul');
this.scrollable.container.classList.add('chatlist-container');
this.scrollable.append(list);
const add = (peerId: number, append: boolean) => {
const {dom} = appDialogsManager.addDialogNew({
dialog: peerId,
container: list,
drawStatus: false,
rippleEnabled: true,
avatarSize: 48,
append
});
const user = appUsersManager.getUser(peerId);
dom.lastMessageSpan.innerHTML = user.pFlags.bot ? ('@' + user.username) : user.rPhone || (user.username ? '@' + user.username : appUsersManager.getUserStatusString(peerId));
};
for(const peerId of this.peerIds) {
add(peerId, true);
}
let target: HTMLElement;
const onUnblock = () => {
const peerId = +target.dataset.peerId;
appUsersManager.toggleBlock(peerId, false);
};
const element = this.menuElement = ButtonMenu([{
icon: 'unlock',
text: 'Unblock',
onClick: onUnblock,
options: {listenerSetter: this.listenerSetter}
}]);
element.id = 'blocked-users-contextmenu';
element.classList.add('contextmenu');
document.getElementById('page-chats').append(element);
attachContextMenuListener(this.scrollable.container, (e) => {
target = findUpTag(e.target, 'LI');
if(!target) {
return;
}
if(e instanceof MouseEvent) e.preventDefault();
// smth
if(e instanceof MouseEvent) e.cancelBubble = true;
positionMenu(e, element);
openBtnMenu(element);
}, this.listenerSetter);
this.listenerSetter.add(rootScope, 'peer_block', (update) => {
const {peerId, blocked} = update;
if(blocked) {
add(peerId, false);
} else {
const li = list.querySelector(`[data-peer-id="${peerId}"]`);
if(li) {
li.remove();
}
}
});
}
onCloseAfterTimeout() {
if(this.menuElement) {
this.menuElement.remove();
}
return super.onCloseAfterTimeout();
}
}

20
src/components/sidebarLeft/tabs/privacyAndSecurity.ts

@ -15,6 +15,8 @@ import AppPrivacyAddToGroupsTab from "./privacy/addToGroups";
import AppPrivacyCallsTab from "./privacy/calls"; import AppPrivacyCallsTab from "./privacy/calls";
import AppActiveSessionsTab from "./activeSessions"; import AppActiveSessionsTab from "./activeSessions";
import apiManager from "../../../lib/mtproto/mtprotoworker"; import apiManager from "../../../lib/mtproto/mtprotoworker";
import AppBlockedUsersTab from "./blockedUsers";
import appUsersManager from "../../../lib/appManagers/appUsersManager";
export default class AppPrivacyAndSecurityTab extends SliderSuperTab { export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
protected init() { protected init() {
@ -26,12 +28,18 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
{ {
const section = new SettingSection({noDelimiter: true}); const section = new SettingSection({noDelimiter: true});
let blockedPeerIds: number[];
const blockedUsersRow = new Row({ const blockedUsersRow = new Row({
icon: 'deleteuser', icon: 'deleteuser',
title: 'Blocked Users', title: 'Blocked Users',
subtitle: '6 users', subtitle: 'Loading...',
clickable: true clickable: () => {
const tab = new AppBlockedUsersTab(this.slider);
tab.peerIds = blockedPeerIds;
tab.open();
}
}); });
blockedUsersRow.freezed = true;
let passwordState: AccountPassword; let passwordState: AccountPassword;
const twoFactorRowOptions = { const twoFactorRowOptions = {
@ -75,6 +83,12 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
section.content.append(blockedUsersRow.container, twoFactorRow.container, activeSessionRow.container); section.content.append(blockedUsersRow.container, twoFactorRow.container, activeSessionRow.container);
this.scrollable.append(section.container); this.scrollable.append(section.container);
appUsersManager.getBlocked().then(res => {
blockedUsersRow.freezed = false;
blockedUsersRow.subtitle.innerText = res.count + ' ' + (res.count !== 1 ? 'users' : 'user');
blockedPeerIds = res.peerIds;
});
passwordManager.getState().then(state => { passwordManager.getState().then(state => {
passwordState = state; passwordState = state;
twoFactorRow.subtitle.innerText = state.pFlags.has_password ? 'On' : 'Off'; twoFactorRow.subtitle.innerText = state.pFlags.has_password ? 'On' : 'Off';
@ -87,7 +101,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTab {
apiManager.invokeApi('account.getAuthorizations').then(auths => { apiManager.invokeApi('account.getAuthorizations').then(auths => {
activeSessionRow.freezed = false; activeSessionRow.freezed = false;
authorizations = auths.authorizations; authorizations = auths.authorizations;
activeSessionRow.subtitle.innerText = authorizations.length + ' ' + (authorizations.length > 1 ? 'devices' : 'device'); activeSessionRow.subtitle.innerText = authorizations.length + ' ' + (authorizations.length !== 1 ? 'devices' : 'device');
console.log('auths', auths); console.log('auths', auths);
}); });
} }

8
src/components/sliderTab.ts

@ -1,4 +1,5 @@
import EventListenerBase from "../helpers/eventListenerBase"; import EventListenerBase from "../helpers/eventListenerBase";
import ListenerSetter from "../helpers/listenerSetter";
import ButtonIcon from "./buttonIcon"; import ButtonIcon from "./buttonIcon";
import Scrollable from "./scrollable"; import Scrollable from "./scrollable";
import SidebarSlider from "./slider"; import SidebarSlider from "./slider";
@ -26,6 +27,7 @@ export default class SliderSuperTab implements SliderTab {
public slider: SidebarSlider; public slider: SidebarSlider;
public destroyable: boolean; public destroyable: boolean;
public listenerSetter: ListenerSetter;
constructor(slider: SidebarSlider, destroyable?: boolean) { constructor(slider: SidebarSlider, destroyable?: boolean) {
this._constructor(slider, destroyable); this._constructor(slider, destroyable);
@ -56,6 +58,8 @@ export default class SliderSuperTab implements SliderTab {
this.container.append(this.header, this.content); this.container.append(this.header, this.content);
this.slider.addTab(this); this.slider.addTab(this);
this.listenerSetter = new ListenerSetter();
} }
public close() { public close() {
@ -83,6 +87,10 @@ export default class SliderSuperTab implements SliderTab {
this.slider.tabs.delete(this); this.slider.tabs.delete(this);
this.container.remove(); this.container.remove();
} }
if(this.listenerSetter) {
this.listenerSetter.removeAll();
}
} }
} }

53
src/helpers/eventListenerBase.ts

@ -1,5 +1,48 @@
//import { MOUNT_CLASS_TO } from "../config/debug";
import type { ArgumentTypes, SuperReturnType } from "../types"; import type { ArgumentTypes, SuperReturnType } from "../types";
// class EventSystem {
// wm: WeakMap<any, Record<any, Set<any>>> = new WeakMap();
// add(target: any, event: any, listener: any) {
// let listeners = this.wm.get(target);
// if (listeners === undefined) {
// listeners = {};
// }
// let listenersForEvent = listeners[event];
// if (listenersForEvent === undefined) {
// listenersForEvent = new Set();
// }
// listenersForEvent.add(listener);
// listeners[event] = listenersForEvent;
// //target.addEventListener(event, listener);
// this.wm.set(target, listeners);
// };
// remove(target: any, event: any, listener: any) {
// let listeners = this.wm.get(target);
// if (!listeners) return;
// let listenersForEvent = listeners[event];
// if (!listenersForEvent) return;
// listenersForEvent.delete(listener);
// };
// /* fire(target, event) {
// let listeners = this.wm.get(target);
// if (!listeners) return;
// let listenersForEvent = listeners[event];
// if (!listenersForEvent) return;
// for (let handler of handlers) {
// setTimeout(handler, 0, event, target); // we use a setTimeout here because we want event triggering to be asynchronous.
// }
// }; */
// }
// console.log = () => {};
// const e = new EventSystem();
// MOUNT_CLASS_TO && (MOUNT_CLASS_TO.e = e);
/** /**
* Better not to remove listeners during setting * Better not to remove listeners during setting
* Should add listener callback only once * Should add listener callback only once
@ -34,12 +77,14 @@ export default class EventListenerBase<Listeners extends {[name: string]: Functi
} }
(this.listeners[name] ?? (this.listeners[name] = [])).push({callback, once}); (this.listeners[name] ?? (this.listeners[name] = [])).push({callback, once});
//e.add(this, name, {callback, once});
} }
public removeListener(name: keyof Listeners, callback: Listeners[typeof name]) { public removeListener(name: keyof Listeners, callback: Listeners[typeof name]) {
if(this.listeners[name]) { if(this.listeners[name]) {
this.listeners[name].findAndSplice(l => l.callback === callback); this.listeners[name].findAndSplice(l => l.callback === callback);
} }
//e.remove(this, name, callback);
} }
// * must be protected, but who cares // * must be protected, but who cares
@ -49,12 +94,16 @@ export default class EventListenerBase<Listeners extends {[name: string]: Functi
} }
const arr: Array<SuperReturnType<Listeners[typeof name]>> = []; const arr: Array<SuperReturnType<Listeners[typeof name]>> = [];
/* let a = e.wm.get(this)[name];
if(!a) return arr;
const listeners = [...a]; */
const listeners = this.listeners[name]; const listeners = this.listeners[name];
if(listeners) { if(listeners) {
// ! this one will guarantee execution even if delete another listener during setting // ! this one will guarantee execution even if delete another listener during setting
const left = listeners.slice(); const left = listeners.slice();
left.forEach(listener => { left.forEach((listener: any) => {
const index = listeners.findIndex(l => l.callback === listener.callback); const index = listeners.findIndex((l: any) => l.callback === listener.callback);
if(index === -1) { if(index === -1) {
return; return;
} }

20
src/helpers/listenerSetter.ts

@ -6,23 +6,23 @@ export type ListenerCallback = (...args: any[]) => any;
export default class ListenerSetter { export default class ListenerSetter {
private listeners: Set<Listener> = new Set(); private listeners: Set<Listener> = new Set();
public add = (element: ListenerElement, event: ListenerEvent, callback: ListenerCallback, options?: ListenerOptions) => { public add(element: ListenerElement, event: ListenerEvent, callback: ListenerCallback, options?: ListenerOptions) {
const listener = {element, event, callback, options}; const listener = {element, event, callback, options};
this.addManual(listener); this.addManual(listener);
return listener; return listener;
}; }
public addManual = (listener: Listener) => { public addManual(listener: Listener) {
listener.element.addEventListener(listener.event, listener.callback, listener.options); listener.element.addEventListener(listener.event, listener.callback, listener.options);
this.listeners.add(listener); this.listeners.add(listener);
}; }
public remove = (listener: Listener) => { public remove(listener: Listener) {
listener.element.removeEventListener(listener.event, listener.callback, listener.options); listener.element.removeEventListener(listener.event, listener.callback, listener.options);
this.listeners.delete(listener); this.listeners.delete(listener);
}; }
public removeManual = (element: ListenerElement, event: ListenerEvent, callback: ListenerCallback, options?: ListenerOptions) => { public removeManual(element: ListenerElement, event: ListenerEvent, callback: ListenerCallback, options?: ListenerOptions) {
let listener: Listener; let listener: Listener;
for(const _listener of this.listeners) { for(const _listener of this.listeners) {
if(_listener.element === element && _listener.event === event && _listener.callback === callback && _listener.options === options) { if(_listener.element === element && _listener.event === event && _listener.callback === callback && _listener.options === options) {
@ -34,11 +34,11 @@ export default class ListenerSetter {
if(listener) { if(listener) {
this.remove(listener); this.remove(listener);
} }
}; }
public removeAll = () => { public removeAll() {
this.listeners.forEach(listener => { this.listeners.forEach(listener => {
this.remove(listener); this.remove(listener);
}); });
}; }
} }

26
src/lib/appManagers/appPeersManager.ts

@ -1,6 +1,6 @@
import { MOUNT_CLASS_TO } from "../../config/debug"; import { MOUNT_CLASS_TO } from "../../config/debug";
import { isObject } from "../../helpers/object"; import { isObject } from "../../helpers/object";
import { DialogPeer, InputDialogPeer, InputPeer, Peer } from "../../layer"; import { DialogPeer, InputDialogPeer, InputPeer, Peer, Update } from "../../layer";
import { RichTextProcessor } from "../richtextprocessor"; import { RichTextProcessor } from "../richtextprocessor";
import rootScope from "../rootScope"; import rootScope from "../rootScope";
import appChatsManager from "./appChatsManager"; import appChatsManager from "./appChatsManager";
@ -24,6 +24,18 @@ const DialogColorsMap = [0, 7, 4, 1, 6, 3, 5];
export type PeerType = 'channel' | 'chat' | 'megagroup' | 'group' | 'saved'; export type PeerType = 'channel' | 'chat' | 'megagroup' | 'group' | 'saved';
export class AppPeersManager { export class AppPeersManager {
constructor() {
rootScope.on('apiUpdate', (e) => {
const update = e as Update;
//console.log('on apiUpdate', update);
switch(update._) {
case 'updatePeerBlocked': {
rootScope.broadcast('peer_block', {peerId: this.getPeerId(update.peer_id), blocked: update.blocked});
break;
}
}
});
}
/* public savePeerInstance(peerId: number, instance: any) { /* public savePeerInstance(peerId: number, instance: any) {
if(peerId < 0) appChatsManager.saveApiChat(instance); if(peerId < 0) appChatsManager.saveApiChat(instance);
else appUsersManager.saveApiUser(instance); else appUsersManager.saveApiUser(instance);
@ -113,13 +125,13 @@ export class AppPeersManager {
: appChatsManager.getChat(-peerId) : appChatsManager.getChat(-peerId)
} }
public getPeerId(peerString: any/* Peer | number | string */): number { public getPeerId(peerId: any/* Peer | number | string */): number {
if(typeof(peerString) === 'number') return peerString; if(typeof(peerId) === 'number') return peerId;
else if(isObject(peerString)) return peerString.user_id ? peerString.user_id : -(peerString.channel_id || peerString.chat_id); else if(isObject(peerId)) return peerId.user_id ? peerId.user_id : -(peerId.channel_id || peerId.chat_id);
else if(!peerString) return 0; else if(!peerId) return 0;
const isUser = peerString.charAt(0) === 'u'; const isUser = peerId.charAt(0) === 'u';
const peerParams = peerString.substr(1).split('_'); const peerParams = peerId.substr(1).split('_');
return isUser ? peerParams[0] : -peerParams[0] || 0; return isUser ? peerParams[0] : -peerParams[0] || 0;
} }

42
src/lib/appManagers/appUsersManager.ts

@ -84,17 +84,6 @@ export class AppUsersManager {
break; break;
} }
/* // @ts-ignore
case 'updateUserBlocked': {
const id = (update as any).user_id;
const blocked: boolean = (update as any).blocked;
const user = this.getUser(id);
if(user) {
}
break;
} */
/* case 'updateContactLink': /* case 'updateContactLink':
this.onContactUpdated(update.user_id, update.my_link._ === 'contactLinkContact'); this.onContactUpdated(update.user_id, update.my_link._ === 'contactLinkContact');
break; */ break; */
@ -225,6 +214,25 @@ export class AppUsersManager {
}); });
} }
public toggleBlock(peerId: number, block: boolean) {
return apiManager.invokeApi(block ? 'contacts.block' : 'contacts.unblock', {
id: appPeersManager.getInputPeerById(peerId)
}).then(value => {
if(value) {
apiUpdatesManager.processUpdateMessage({
_: 'updateShort',
update: {
_: 'updatePeerBlocked',
peer_id: appPeersManager.getOutputPeer(peerId),
blocked: block
} as Update.updatePeerBlocked
});
}
return value;
});
}
public testSelfSearch(query: string) { public testSelfSearch(query: string) {
const user = this.getSelf(); const user = this.getSelf();
const index = searchIndexManager.createIndex(); const index = searchIndexManager.createIndex();
@ -633,6 +641,18 @@ export class AppUsersManager {
}); });
} }
public getBlocked(offset = 0, limit = 0) {
return apiManager.invokeApi('contacts.getBlocked', {offset, limit}).then(contactsBlocked => {
this.saveApiUsers(contactsBlocked.users);
appChatsManager.saveApiChats(contactsBlocked.chats);
const count = contactsBlocked._ === 'contacts.blocked' ? contactsBlocked.users.length + contactsBlocked.chats.length : contactsBlocked.count;
const peerIds = contactsBlocked.users.map(u => u.id).concat(contactsBlocked.chats.map(c => -c.id));
return {count, peerIds};
});
}
/* public searchContacts(query: string, limit = 20) { /* public searchContacts(query: string, limit = 20) {
return Promise.all([ return Promise.all([
this.getContacts(query), this.getContacts(query),

5
src/lib/rootScope.ts

@ -20,6 +20,7 @@ type BroadcastEvents = {
'peer_pinned_messages': {peerId: number, mids?: number[], pinned?: boolean, unpinAll?: true}, 'peer_pinned_messages': {peerId: number, mids?: number[], pinned?: boolean, unpinAll?: true},
'peer_pinned_hidden': {peerId: number, maxId: number}, 'peer_pinned_hidden': {peerId: number, maxId: number},
'peer_typings': {peerId: number, typings: UserTyping[]}, 'peer_typings': {peerId: number, typings: UserTyping[]},
'peer_block': {peerId: number, blocked: boolean},
'filter_delete': MyDialogFilter, 'filter_delete': MyDialogFilter,
'filter_update': MyDialogFilter, 'filter_update': MyDialogFilter,
@ -125,11 +126,11 @@ class RootScope extends EventListenerBase<any> {
} }
public broadcast = <T extends keyof BroadcastEvents>(name: T, detail?: BroadcastEvents[T]) => { public broadcast = <T extends keyof BroadcastEvents>(name: T, detail?: BroadcastEvents[T]) => {
/* if(DEBUG) { //if(DEBUG) {
if(name !== 'user_update') { if(name !== 'user_update') {
console.debug('Broadcasting ' + name + ' event, with args:', detail); console.debug('Broadcasting ' + name + ' event, with args:', detail);
} }
} */ //}
this.setListenerResult(name, detail); this.setListenerResult(name, detail);
}; };

6
src/scss/partials/_leftSidebar.scss

@ -1030,6 +1030,12 @@
} }
} }
.blocked-users-container {
.sidebar-left-section-caption {
font-size: 1rem;
}
}
.range-setting-selector { .range-setting-selector {
padding: 1rem .875rem; padding: 1rem .875rem;

Loading…
Cancel
Save