User permissions
Fix forward layout
This commit is contained in:
parent
2a6e7d1178
commit
5f00fb9a87
@ -91,6 +91,8 @@ export class AppNavigationController {
|
||||
}, options);
|
||||
}
|
||||
|
||||
history.scrollRestoration = 'manual';
|
||||
|
||||
this.pushState(); // * push init state
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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<any>;
|
||||
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<number> = 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<any>[] = [];
|
||||
|
||||
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);
|
||||
}
|
||||
@ -313,6 +377,10 @@ export default class AppSelectPeers {
|
||||
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) {
|
||||
|
@ -8,7 +8,7 @@ export default class PopupPeer extends PopupElement {
|
||||
description: string,
|
||||
buttons: Array<PopupButton>
|
||||
}> = {}) {
|
||||
super('popup-peer' + (className ? ' ' + className : ''), options.buttons);
|
||||
super('popup-peer' + (className ? ' ' + className : ''), options.buttons, {overlayClosable: true});
|
||||
|
||||
let avatarEl = new AvatarElement();
|
||||
avatarEl.setAttribute('dialog', '1');
|
||||
|
@ -10,7 +10,8 @@ export default class PopupPickUser extends PopupElement {
|
||||
onSelect?: (peerId: number) => Promise<void> | 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);
|
||||
|
@ -22,7 +22,7 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise<bool
|
||||
//let animationEndPromise: Promise<number>;
|
||||
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<bool
|
||||
let elapsedTime = Date.now() - startTime;
|
||||
if(elapsedTime < duration) {
|
||||
let delay = Math.max(duration - elapsedTime, duration / 2);
|
||||
setTimeout(() => 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<bool
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
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<bool
|
||||
|
||||
//console.log('ripple click', offsetFromCenter, size, clickX, clickY);
|
||||
|
||||
span.style.width = span.style.height = size + 'px';
|
||||
span.style.left = x + 'px';
|
||||
span.style.top = y + 'px';
|
||||
elem.style.width = elem.style.height = size + 'px';
|
||||
elem.style.left = x + 'px';
|
||||
elem.style.top = y + 'px';
|
||||
|
||||
// нижний код выполняется с задержкой
|
||||
/* animationEndPromise = new Promise((resolve) => {
|
||||
@ -124,7 +124,7 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise<bool
|
||||
duration = +window.getComputedStyle(span).getPropertyValue('animation-duration').replace('s', '') * 1000;
|
||||
span.style.display = ''; */
|
||||
|
||||
r.append(span);
|
||||
r.append(elem);
|
||||
|
||||
//r.classList.add('active');
|
||||
//handler();
|
||||
|
@ -39,7 +39,7 @@ export default class AppBlockedUsersTab extends SliderSuperTab {
|
||||
});
|
||||
}, {listenerSetter: this.listenerSetter});
|
||||
|
||||
const list = document.createElement('ul');
|
||||
const list = appDialogsManager.createChatList();
|
||||
this.scrollable.container.classList.add('chatlist-container');
|
||||
this.scrollable.append(list);
|
||||
|
||||
@ -122,7 +122,7 @@ export default class AppBlockedUsersTab extends SliderSuperTab {
|
||||
add(peerId, true);
|
||||
}
|
||||
|
||||
if(res.peerIds.length < LOAD_COUNT) {
|
||||
if(res.peerIds.length < LOAD_COUNT || list.childElementCount === res.count) {
|
||||
this.scrollable.onScrolledBottom = null;
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,7 @@ export default class AppContactsTab extends SliderSuperTab {
|
||||
init() {
|
||||
this.container.id = 'contacts-container';
|
||||
|
||||
this.list = document.createElement('ul');
|
||||
this.list = appDialogsManager.createChatList(/* {avatarSize: 48, handheldsSize: 66} */);
|
||||
this.list.id = 'contacts';
|
||||
this.list.classList.add('contacts-container');
|
||||
|
||||
|
@ -254,7 +254,7 @@ export default class AppEditFolderTab extends SliderSuperTab {
|
||||
|
||||
(['include_peers', 'exclude_peers'] as ['include_peers', 'exclude_peers']).forEach(key => {
|
||||
const container = this[key];
|
||||
const ul = document.createElement('ul');
|
||||
const ul = appDialogsManager.createChatList();
|
||||
|
||||
const peers = filter[key].slice();
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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, () => {
|
||||
|
95
src/components/sidebarRight/tabs/userPermissions.ts
Normal file
95
src/components/sidebarRight/tabs/userPermissions.ts
Normal file
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
28
src/helpers/listLoader.ts
Normal file
28
src/helpers/listLoader.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import Scrollable from "../components/scrollable";
|
||||
|
||||
export default class ScrollableLoader {
|
||||
constructor(options: {
|
||||
scrollable: Scrollable,
|
||||
getPromise: () => Promise<any>
|
||||
}) {
|
||||
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;
|
||||
});
|
||||
};
|
||||
}
|
||||
}
|
@ -121,7 +121,7 @@
|
||||
</div>
|
||||
<div class="tabs-container" id="folders-container">
|
||||
<div>
|
||||
<ul id="dialogs"></ul>
|
||||
<ul id="dialogs" class="chatlist chatlist-72"></ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -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();
|
||||
|
@ -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<HTMLElement> = 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
|
||||
};
|
||||
|
||||
|
@ -53,6 +53,21 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
|
||||
|
||||
private hashes: {[method: string]: HashOptions} = {};
|
||||
|
||||
private apiPromisesSingle: {
|
||||
[q: string]: Promise<any>
|
||||
} = {};
|
||||
private apiPromisesCacheable: {
|
||||
[method: string]: {
|
||||
[queryJSON: string]: {
|
||||
timestamp: number,
|
||||
promise: Promise<any>,
|
||||
fulfilled: boolean,
|
||||
timeout?: number,
|
||||
params: any
|
||||
}
|
||||
}
|
||||
} = {};
|
||||
|
||||
private isSWRegistered = true;
|
||||
|
||||
private debug = DEBUG /* && false */;
|
||||
@ -354,6 +369,71 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
|
||||
});
|
||||
}
|
||||
|
||||
public invokeApiSingle<T extends keyof MethodDeclMap>(method: T, params: MethodDeclMap[T]['req'] = {} as any, options: InvokeApiOptions = {}): Promise<MethodDeclMap[T]['res']> {
|
||||
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<T extends keyof MethodDeclMap>(method: T, params: MethodDeclMap[T]['req'] = {} as any, options: InvokeApiOptions & Partial<{cacheSeconds: number, override: boolean}> = {}): Promise<MethodDeclMap[T]['res']> {
|
||||
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<T extends keyof MethodDeclMap>(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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user