Group type tab

This commit is contained in:
Eduard Kuzmenko 2021-03-16 19:18:51 +04:00
parent 692aa02c54
commit a4821aa08e
21 changed files with 468 additions and 137 deletions

View File

@ -161,4 +161,4 @@ export default class AvatarElement extends HTMLElement {
}
}
customElements.define("avatar-element", AvatarElement);
customElements.define("avatar-element", AvatarElement);

View File

@ -27,4 +27,4 @@ const Button = (className: string, options: Partial<{noRipple: true, onlyMobile:
return button;
};
export default Button;
export default Button;

View File

@ -5,4 +5,4 @@ const ButtonIcon = (className: string, options: Partial<{noRipple: true, onlyMob
return button;
};
export default ButtonIcon;
export default ButtonIcon;

View File

@ -37,7 +37,7 @@ export default class CheckboxField {
if(options.stateKey) {
appStateManager.getState().then(state => {
this.value = getDeepProperty(state, options.stateKey);
this.checked = getDeepProperty(state, options.stateKey);
input.addEventListener('change', () => {
appStateManager.setByKey(options.stateKey, input.checked);
@ -83,18 +83,18 @@ export default class CheckboxField {
}
}
get value() {
get checked() {
return this.input.checked;
}
set value(value: boolean) {
this.setValueSilently(value);
set checked(checked: boolean) {
this.setValueSilently(checked);
const event = new Event('change', {bubbles: true, cancelable: true});
this.input.dispatchEvent(event);
}
public setValueSilently(value: boolean) {
this.input.checked = value;
public setValueSilently(checked: boolean) {
this.input.checked = checked;
}
}

View File

@ -51,6 +51,22 @@ const checkAndSetRTL = (input: HTMLElement) => {
input.style.direction = direction;
};
export enum InputState {
Neutral = 0,
Valid = 1,
Error = 2
};
export type InputFieldOptions = {
placeholder?: string,
label?: string,
name?: string,
maxLength?: number,
showLengthOn?: number,
plainText?: true,
animate?: true
};
class InputField {
public container: HTMLElement;
public input: HTMLElement;
@ -63,15 +79,7 @@ class InputField {
protected wasInputFakeClientHeight: number;
protected showScrollDebounced: () => void;
constructor(protected options: {
placeholder?: string,
label?: string,
name?: string,
maxLength?: number,
showLengthOn?: number,
plainText?: true,
animate?: true
} = {}) {
constructor(public options: InputFieldOptions = {}) {
this.container = document.createElement('div');
this.container.classList.add('input-field');
@ -211,14 +219,31 @@ class InputField {
return !this.input.classList.contains('error') && this.value !== this.originalValue;
}
public setOriginalValue(value: InputField['originalValue']) {
public setOriginalValue(value: InputField['originalValue'] = '', silent = false) {
this.originalValue = value;
if(this.options.plainText) {
this.value = value;
} else {
this.value = RichTextProcessor.wrapDraftText(value);
if(!this.options.plainText) {
value = RichTextProcessor.wrapDraftText(value);
}
if(silent) {
this.setValueSilently(value, false);
} else {
this.value = value;
}
}
public setState(state: InputState, label?: string) {
if(label) {
this.label.innerHTML = label;
}
this.input.classList.toggle('error', !!(state & InputState.Error));
this.input.classList.toggle('valid', !!(state & InputState.Valid));
}
public setError(label?: string) {
this.setState(InputState.Error, label);
}
}

View File

@ -79,6 +79,18 @@ export function putPreloader(elem: Element, returnDiv = false): HTMLElement {
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.putPreloader = putPreloader);
export function setButtonLoader(elem: HTMLButtonElement, icon = 'check') {
elem.classList.remove('tgico-' + icon);
elem.disabled = true;
putPreloader(elem);
return () => {
elem.innerHTML = '';
elem.classList.add('tgico-' + icon);
elem.removeAttribute('disabled');
};
}
let sortedCountries: Country[];
export function formatPhoneNumber(str: string) {
str = str.replace(/\D/g, '');

View File

@ -303,7 +303,10 @@ export default class PopupCreatePoll extends PopupElement {
});
questionField.input.addEventListener('input', this.onInput);
const radioField = new RadioField('', 'question');
const radioField = new RadioField({
text: '',
name: 'question'
});
radioField.main.append(questionField.container);
questionField.input.addEventListener('click', cancelEvent);
radioField.label.classList.add('hidden-widget');

View File

@ -62,7 +62,15 @@ export default class PrivacySection {
const random = randomLong();
r.forEach(({type, text}) => {
this.radioRows.set(type, new Row({radioField: new RadioField(text, random, '' + type)}));
const row = new Row({
radioField: new RadioField({
text,
name: random,
value: '' + type
})
});
this.radioRows.set(type, row);
});
const form = RadioFormFromRows([...this.radioRows.values()], this.onRadioChange);

View File

@ -6,24 +6,29 @@ export default class RadioField {
public label: HTMLLabelElement;
public main: HTMLElement;
constructor(text: string, name: string, value?: string, stateKey?: string) {
constructor(options: {
text?: string,
name: string,
value?: string,
stateKey?: string
}) {
const label = this.label = document.createElement('label');
label.classList.add('radio-field');
const input = this.input = document.createElement('input');
input.type = 'radio';
/* input.id = */input.name = 'input-radio-' + name;
/* input.id = */input.name = 'input-radio-' + options.name;
if(value) {
input.value = value;
if(options.value) {
input.value = options.value;
if(stateKey) {
if(options.stateKey) {
appStateManager.getState().then(state => {
input.checked = getDeepProperty(state, stateKey) === value;
input.checked = getDeepProperty(state, options.stateKey) === options.value;
});
input.addEventListener('change', () => {
appStateManager.setByKey(stateKey, value);
appStateManager.setByKey(options.stateKey, options.value);
});
}
}
@ -31,8 +36,8 @@ export default class RadioField {
const main = this.main = document.createElement('div');
main.classList.add('radio-field-main');
if(text) {
main.innerHTML = text;
if(options.text) {
main.innerHTML = options.text;
/* const caption = document.createElement('div');
caption.classList.add('radio-field-main-caption');
caption.innerHTML = text;
@ -47,4 +52,19 @@ export default class RadioField {
label.append(input, main);
}
get checked() {
return this.input.checked;
}
set checked(checked: boolean) {
this.setValueSilently(checked);
const event = new Event('change', {bubbles: true, cancelable: true});
this.input.dispatchEvent(event);
}
public setValueSilently(checked: boolean) {
this.input.checked = checked;
}
};

View File

@ -1,10 +1,10 @@
import appProfileManager from "../../../lib/appManagers/appProfileManager";
import appUsersManager from "../../../lib/appManagers/appUsersManager";
import apiManager from "../../../lib/mtproto/mtprotoworker";
import InputField from "../../inputField";
import { SliderSuperTab } from "../../slider";
import { attachClickEvent } from "../../../helpers/dom";
import EditPeer from "../../editPeer";
import { UsernameInputField } from "../../usernameInputField";
// TODO: аватарка не поменяется в этой вкладке после изменения почему-то (если поставить в другом клиенте, и потом тут проверить, для этого ещё вышел в чатлист)
@ -12,8 +12,7 @@ export default class AppEditProfileTab extends SliderSuperTab {
private firstNameInputField: InputField;
private lastNameInputField: InputField;
private bioInputField: InputField;
private userNameInputField: InputField;
private userNameInput: HTMLElement;
private usernameInputField: InputField;
private profileUrlContainer: HTMLDivElement;
private profileUrlAnchor: HTMLAnchorElement;
@ -74,14 +73,22 @@ export default class AppEditProfileTab extends SliderSuperTab {
const inputWrapper = document.createElement('div');
inputWrapper.classList.add('input-wrapper');
this.userNameInputField = new InputField({
this.usernameInputField = new UsernameInputField({
peerId: 0,
label: 'Username (optional)',
name: 'username',
plainText: true
plainText: true,
listenerSetter: this.listenerSetter,
onChange: () => {
this.editPeer.handleChange();
this.setProfileUrl();
},
availableText: 'Username is available',
takenText: 'Username is already taken',
invalidText: 'Username is invalid'
});
this.userNameInput = this.userNameInputField.input;
inputWrapper.append(this.userNameInputField.container);
inputWrapper.append(this.usernameInputField.container);
const caption = document.createElement('div');
caption.classList.add('caption');
@ -91,67 +98,10 @@ export default class AppEditProfileTab extends SliderSuperTab {
this.profileUrlContainer = caption.querySelector('.profile-url-container');
this.profileUrlAnchor = this.profileUrlContainer.lastElementChild as HTMLAnchorElement;
inputFields.push(this.userNameInputField);
inputFields.push(this.usernameInputField);
this.scrollable.append(h2, inputWrapper, caption);
}
let userNameLabel = this.userNameInput.nextElementSibling as HTMLLabelElement;
this.listenerSetter.add(this.userNameInput, 'input', () => {
let value = this.userNameInputField.value;
//console.log('userNameInput:', value);
if(value === this.userNameInputField.originalValue || !value.length) {
this.userNameInput.classList.remove('valid', 'error');
userNameLabel.innerText = 'Username (optional)';
this.setProfileUrl();
this.editPeer.handleChange();
return;
} else if(!this.isUsernameValid(value)) { // does not check the last underscore
this.userNameInput.classList.add('error');
this.userNameInput.classList.remove('valid');
userNameLabel.innerText = 'Username is invalid';
} else {
this.userNameInput.classList.remove('valid', 'error');
}
if(this.userNameInput.classList.contains('error')) {
this.setProfileUrl();
this.editPeer.handleChange();
return;
}
apiManager.invokeApi('account.checkUsername', {
username: value
}).then(available => {
if(this.userNameInputField.value !== value) return;
if(available) {
this.userNameInput.classList.add('valid');
this.userNameInput.classList.remove('error');
userNameLabel.innerText = 'Username is available';
} else {
this.userNameInput.classList.add('error');
this.userNameInput.classList.remove('valid');
userNameLabel.innerText = 'Username is already taken';
}
}, (err) => {
if(this.userNameInputField.value !== value) return;
switch(err.type) {
case 'USERNAME_INVALID': {
this.userNameInput.classList.add('error');
this.userNameInput.classList.remove('valid');
userNameLabel.innerText = 'Username is invalid';
break;
}
}
}).then(() => {
this.editPeer.handleChange();
this.setProfileUrl();
});
});
attachClickEvent(this.editPeer.nextBtn, () => {
this.editPeer.nextBtn.disabled = true;
@ -169,8 +119,8 @@ export default class AppEditProfileTab extends SliderSuperTab {
}));
}
if(this.userNameInputField.isValid() && this.userNameInput.classList.contains('valid')) {
promises.push(appProfileManager.updateUsername(this.userNameInputField.value));
if(this.usernameInputField.isValid() && this.usernameInputField.input.classList.contains('valid')) {
promises.push(appUsersManager.updateUsername(this.usernameInputField.value));
}
Promise.race(promises).finally(() => {
@ -187,13 +137,10 @@ export default class AppEditProfileTab extends SliderSuperTab {
const user = appUsersManager.getSelf();
this.firstNameInputField.setOriginalValue(user.first_name);
this.lastNameInputField.setOriginalValue(user.last_name);
this.bioInputField.setOriginalValue('');
this.userNameInputField.setOriginalValue(user.username ?? '');
this.userNameInput.classList.remove('valid', 'error');
this.userNameInput.nextElementSibling.innerHTML = 'Username (optional)';
this.firstNameInputField.setOriginalValue(user.first_name, true);
this.lastNameInputField.setOriginalValue(user.last_name, true);
this.bioInputField.setOriginalValue('', true);
this.usernameInputField.setOriginalValue(user.username, true);
appProfileManager.getProfile(user.id, true).then(userFull => {
if(userFull.about) {
@ -205,16 +152,12 @@ export default class AppEditProfileTab extends SliderSuperTab {
this.editPeer.handleChange();
}
public isUsernameValid(username: string) {
return ((username.length >= 5 && username.length <= 32) || !username.length) && /^[a-zA-Z0-9_]*$/.test(username);
}
private setProfileUrl() {
if(this.userNameInput.classList.contains('error') || !this.userNameInputField.value.length) {
if(this.usernameInputField.input.classList.contains('error') || !this.usernameInputField.value.length) {
this.profileUrlContainer.style.display = 'none';
} else {
this.profileUrlContainer.style.display = '';
let url = 'https://t.me/' + this.userNameInputField.value;
let url = 'https://t.me/' + this.usernameInputField.value;
this.profileUrlAnchor.innerText = url;
this.profileUrlAnchor.href = url;
}

View File

@ -88,12 +88,22 @@ export default class AppGeneralSettingsTab extends SliderSuperTab {
const form = document.createElement('form');
const enterRow = new Row({
radioField: new RadioField('Send by Enter', 'send-shortcut', 'enter', 'settings.sendShortcut'),
radioField: new RadioField({
text: 'Send by Enter',
name: 'send-shortcut',
value: 'enter',
stateKey: 'settings.sendShortcut'
}),
subtitle: 'New line by Shift + Enter',
});
const ctrlEnterRow = new Row({
radioField: new RadioField(`Send by ${isApple ? '⌘' : 'Ctrl'} + Enter`, 'send-shortcut', 'ctrlEnter', 'settings.sendShortcut'),
radioField: new RadioField({
text: `Send by ${isApple ? '⌘' : 'Ctrl'} + Enter`,
name: 'send-shortcut',
value: 'ctrlEnter',
stateKey: 'settings.sendShortcut'
}),
subtitle: 'New line by Enter',
});

View File

@ -43,8 +43,8 @@ export default class AppNotificationsTab extends SliderSuperTabEventable {
(ret instanceof Promise ? ret : Promise.resolve(ret)).then((notifySettings) => {
const applySettings = () => {
const muted = appNotificationsManager.isMuted(notifySettings);
enabledRow.checkboxField.value = !muted;
previewEnabledRow.checkboxField.value = notifySettings.show_previews;
enabledRow.checkboxField.checked = !muted;
previewEnabledRow.checkboxField.checked = notifySettings.show_previews;
return muted;
};
@ -52,8 +52,8 @@ export default class AppNotificationsTab extends SliderSuperTabEventable {
applySettings();
this.eventListener.addListener('destroy', () => {
const mute = !enabledRow.checkboxField.value;
const showPreviews = previewEnabledRow.checkboxField.value;
const mute = !enabledRow.checkboxField.checked;
const showPreviews = previewEnabledRow.checkboxField.checked;
if(mute === appNotificationsManager.isMuted(notifySettings) && showPreviews === notifySettings.show_previews) {
return;
@ -115,10 +115,10 @@ export default class AppNotificationsTab extends SliderSuperTabEventable {
this.scrollable.append(section.container);
appNotificationsManager.getContactSignUpNotification().then(enabled => {
contactsSignUpRow.checkboxField.value = enabled;
contactsSignUpRow.checkboxField.checked = enabled;
this.eventListener.addListener('destroy', () => {
const _enabled = contactsSignUpRow.checkboxField.value;
const _enabled = contactsSignUpRow.checkboxField.checked;
if(enabled !== _enabled) {
appNotificationsManager.setContactSignUpNotification(!_enabled);
}

View File

@ -81,8 +81,8 @@ export default class AppEditContactTab extends SliderSuperTab {
const peerId = appPeersManager.getPeerId(update.peer.peer);
if(this.peerId === peerId) {
const enabled = !appNotificationsManager.isMuted(update.notify_settings);
if(enabled !== notificationsCheckboxField.value) {
notificationsCheckboxField.value = enabled;
if(enabled !== notificationsCheckboxField.checked) {
notificationsCheckboxField.checked = enabled;
}
}
});
@ -92,7 +92,7 @@ export default class AppEditContactTab extends SliderSuperTab {
});
const enabled = !appNotificationsManager.isPeerLocalMuted(this.peerId, false);
notificationsCheckboxField.value = enabled;
notificationsCheckboxField.checked = enabled;
const profileNameDiv = document.createElement('div');
profileNameDiv.classList.add('profile-name');

View File

@ -11,6 +11,7 @@ import { attachClickEvent, toggleDisability } from "../../../helpers/dom";
import { ChatFull } from "../../../layer";
import PopupPeer from "../../popups/peer";
import { addCancelButton } from "../../popups";
import AppGroupTypeTab from "./groupType";
export default class AppEditGroupTab extends SliderSuperTab {
private groupNameInputField: InputField;
@ -41,8 +42,10 @@ export default class AppEditGroupTab extends SliderSuperTab {
name: 'group-description',
maxLength: 255
});
const chat = appChatsManager.getChat(-this.peerId);
this.groupNameInputField.setOriginalValue(appChatsManager.getChat(-this.peerId).title);
this.groupNameInputField.setOriginalValue(chat.title);
this.descriptionInputField.setOriginalValue(chatFull.about);
@ -62,8 +65,13 @@ export default class AppEditGroupTab extends SliderSuperTab {
if(appChatsManager.hasRights(-this.peerId, 'change_type')) {
const groupTypeRow = new Row({
title: 'Group Type',
subtitle: 'Private',
clickable: true,
subtitle: chat.username ? 'Public' : 'Private',
clickable: () => {
const tab = new AppGroupTypeTab(this.slider);
tab.peerId = this.peerId;
tab.chatFull = chatFull;
tab.open();
},
icon: 'lock'
});
@ -139,7 +147,7 @@ export default class AppEditGroupTab extends SliderSuperTab {
});
if(appChatsManager.isChannel(-this.peerId) && !(chatFull as ChatFull.channelFull).pFlags.hidden_prehistory) {
showChatHistoryCheckboxField.value = true;
showChatHistoryCheckboxField.checked = true;
}
section.content.append(showChatHistoryCheckboxField.label);

View File

@ -0,0 +1,148 @@
import { copyTextToClipboard } from "../../../helpers/clipboard";
import { attachClickEvent, toggleDisability } from "../../../helpers/dom";
import { randomLong } from "../../../helpers/random";
import { Chat, ChatFull, ExportedChatInvite } from "../../../layer";
import appChatsManager from "../../../lib/appManagers/appChatsManager";
import appProfileManager from "../../../lib/appManagers/appProfileManager";
import Button from "../../button";
import { setButtonLoader } from "../../misc";
import PopupConfirmAction from "../../popups/confirmAction";
import RadioField from "../../radioField";
import Row, { RadioFormFromRows } from "../../row";
import { SettingSection } from "../../sidebarLeft";
import { SliderSuperTab } from "../../slider";
import { toast } from "../../toast";
import { UsernameInputField } from "../../usernameInputField";
export default class AppGroupTypeTab extends SliderSuperTab {
public peerId: number;
public chatFull: ChatFull;
protected init() {
this.container.classList.add('edit-peer-container', 'group-type-container');
this.title.innerHTML = 'Group Type';
const section = new SettingSection({
name: 'Group Type'
});
const random = randomLong();
const privateRow = new Row({
radioField: new RadioField({
text: 'Private Group',
name: random,
value: 'private'
}),
subtitle: 'Private groups can only be joined if you were invited or have an invite link.'
});
const publicRow = new Row({
radioField: new RadioField({
text: 'Public Group',
name: random,
value: 'public'
}),
subtitle: 'Public groups can be found in search, history is available to everyone and anyone can join.'
});
const form = RadioFormFromRows([privateRow, publicRow], (value) => {
const a = [privateSection, publicSection];
if(value === 'public') a.reverse();
a[0].container.classList.remove('hide');
a[1].container.classList.add('hide');
onChange();
});
const chat: Chat = appChatsManager.getChat(-this.peerId);
section.content.append(form);
const privateSection = new SettingSection({});
//let revoked = false;
const linkRow = new Row({
title: (this.chatFull.exported_invite as ExportedChatInvite.chatInviteExported).link,
subtitle: 'People can join your group by following this link. You can revoke the link at any time.',
clickable: () => {
copyTextToClipboard((this.chatFull.exported_invite as ExportedChatInvite.chatInviteExported).link);
toast('Invite link copied to clipboard.');
}
});
const btnRevoke = Button('btn-primary btn-transparent danger', {icon: 'delete', text: 'Revoke Link'});
attachClickEvent(btnRevoke, () => {
new PopupConfirmAction('revoke-link', [{
text: 'OK',
callback: () => {
toggleDisability([btnRevoke], true);
appProfileManager.getChatInviteLink(-this.peerId, true).then(link => {
toggleDisability([btnRevoke], false);
linkRow.title.innerHTML = link;
//revoked = true;
//onChange();
});
}
}], {
title: 'Revoke Link?',
text: 'Your previous link will be deactivated and we\'ll generate a new invite link for you.'
}).show();
}, {listenerSetter: this.listenerSetter});
privateSection.content.append(linkRow.container, btnRevoke);
const publicSection = new SettingSection({
caption: 'People can share this link with others and find your group using Telegram search.',
noDelimiter: true
});
const inputWrapper = document.createElement('div');
inputWrapper.classList.add('input-wrapper');
const placeholder = 't.me/';
const onChange = () => {
const changed = (privateRow.radioField.checked && (originalValue !== placeholder/* || revoked */))
|| (linkInputField.isValid() && linkInputField.input.classList.contains('valid'));
applyBtn.classList.toggle('is-visible', changed);
};
const linkInputField = new UsernameInputField({
label: 'Link',
name: 'group-public-link',
plainText: true,
listenerSetter: this.listenerSetter,
availableText: 'Link is available',
invalidText: 'Link is invalid',
takenText: 'Link is already taken',
onChange: onChange,
peerId: this.peerId,
head: placeholder
});
const originalValue = placeholder + ((chat as Chat.channel).username || '');
inputWrapper.append(linkInputField.container)
publicSection.content.append(inputWrapper);
const applyBtn = Button('btn-circle btn-corner tgico-check is-visible');
this.content.append(applyBtn);
attachClickEvent(applyBtn, () => {
const unsetLoader = setButtonLoader(applyBtn);
const username = publicRow.radioField.checked ? linkInputField.getValue() : '';
appChatsManager.migrateChat(-this.peerId).then(channelId => {
return appChatsManager.updateUsername(channelId, username);
}).then(() => {
unsetLoader();
this.close();
});
}, {listenerSetter: this.listenerSetter});
(originalValue !== placeholder ? publicRow : privateRow).radioField.checked = true;
linkInputField.setOriginalValue(originalValue);
this.scrollable.append(section.container, privateSection.container, publicSection.container);
}
}

View File

@ -0,0 +1,98 @@
import ListenerSetter from "../helpers/listenerSetter";
import { debounce } from "../helpers/schedulers";
import appChatsManager from "../lib/appManagers/appChatsManager";
import apiManager from "../lib/mtproto/mtprotoworker";
import RichTextProcessor from "../lib/richtextprocessor";
import InputField, { InputFieldOptions, InputState } from "./inputField";
export class UsernameInputField extends InputField {
private checkUsernamePromise: Promise<any>;
private checkUsernameDebounced: (username: string) => void;
public options: InputFieldOptions & {
peerId: number,
listenerSetter: ListenerSetter,
onChange?: () => void,
invalidText: string,
takenText: string,
availableText: string,
head?: string
};
constructor(options: UsernameInputField['options']) {
super(options);
this.checkUsernameDebounced = debounce(this.checkUsername.bind(this), 150, false, true);
options.listenerSetter.add(this.input, 'input', () => {
const value = this.getValue();
//console.log('userNameInput:', value);
if(value === this.originalValue || !value.length) {
this.setState(InputState.Neutral, this.options.label);
this.options.onChange && this.options.onChange();
return;
} else if(!RichTextProcessor.isUsernameValid(value)) { // does not check the last underscore
this.setError(this.options.invalidText);
} else {
this.setState(InputState.Neutral);
}
if(this.input.classList.contains('error')) {
this.options.onChange && this.options.onChange();
return;
}
this.checkUsernameDebounced(value);
});
}
public getValue() {
let value = this.value;
if(this.options.head) {
value = value.slice(this.options.head.length);
this.setValueSilently(this.options.head + value);
}
return value;
}
private checkUsername(username: string) {
if(this.checkUsernamePromise) return;
if(this.options.peerId) {
this.checkUsernamePromise = apiManager.invokeApi('channels.checkUsername', {
channel: appChatsManager.getChannelInput(-this.options.peerId),
username
});
} else {
this.checkUsernamePromise = apiManager.invokeApi('account.checkUsername', {username});
}
this.checkUsernamePromise.then(available => {
if(this.getValue() !== username) return;
if(available) {
this.setState(InputState.Valid, this.options.availableText);
} else {
this.setError(this.options.takenText);
}
}, (err) => {
if(this.getValue() !== username) return;
switch(err.type) {
case 'USERNAME_INVALID': {
this.setError(this.options.invalidText);
break;
}
}
}).then(() => {
this.checkUsernamePromise = undefined;
this.options.onChange && this.options.onChange();
const value = this.getValue();
if(value !== username && this.isValid() && RichTextProcessor.isUsernameValid(value)) {
this.checkUsername(value);
}
});
};
}

View File

@ -1,7 +1,7 @@
import { MOUNT_CLASS_TO } from "../../config/debug";
import { numberThousandSplitter } from "../../helpers/number";
import { isObject, safeReplaceObject, copy } from "../../helpers/object";
import { Chat, ChatAdminRights, ChatBannedRights, ChatFull, ChatParticipants, InputChannel, InputChatPhoto, InputFile, InputPeer, SendMessageAction, Updates } from "../../layer";
import { Chat, ChatAdminRights, ChatBannedRights, ChatFull, ChatParticipants, InputChannel, InputChatPhoto, InputFile, InputPeer, SendMessageAction, Update, Updates } from "../../layer";
import apiManager from '../mtproto/mtprotoworker';
import { RichTextProcessor } from "../richtextprocessor";
import rootScope from "../rootScope";
@ -276,11 +276,18 @@ export class AppChatsManager {
public getChannelInput(id: number): InputChannel {
if(id < 0) id = -id;
return {
_: 'inputChannel',
channel_id: id,
access_hash: this.getChat(id).access_hash/* || this.channelAccess[id] */ || 0
};
const chat: Chat = this.getChat(id);
if(chat._ === 'chatEmpty' || !(chat as Chat.channel).access_hash) {
return {
_: 'inputChannelEmpty'
};
} else {
return {
_: 'inputChannel',
channel_id: id,
access_hash: (chat as Chat.channel).access_hash/* || this.channelAccess[id] */ || '0'
};
}
}
public getChatInputPeer(id: number): InputPeer.inputPeerChat {
@ -519,10 +526,30 @@ export class AppChatsManager {
}).then(this.onChatUpdated.bind(this, id));
}
public migrateChat(id: number) {
public migrateChat(id: number): Promise<number> {
const chat: Chat = this.getChat(id);
if(chat._ === 'channel') return Promise.resolve(chat.id);
return apiManager.invokeApi('messages.migrateChat', {
chat_id: id
}).then(this.onChatUpdated.bind(this, id));
}).then((updates) => {
this.onChatUpdated(id, updates);
const update: Update.updateChannel = (updates as Updates.updates).updates.find(u => u._ === 'updateChannel') as any;
return update.channel_id;
});
}
public updateUsername(id: number, username: string) {
return apiManager.invokeApi('channels.updateUsername', {
channel: this.getChannelInput(id),
username
}).then((bool) => {
if(bool) {
const chat: Chat.channel = this.getChat(id);
chat.username = username;
}
return bool;
});
}
public editPhoto(id: number, inputFile: InputFile) {

View File

@ -737,6 +737,14 @@ export class AppUsersManager {
}
}
public updateUsername(username: string) {
return apiManager.invokeApi('account.updateUsername', {
username
}).then((user) => {
this.saveApiUser(user);
});
}
public setUserStatus(userId: number, offline: boolean) {
if(this.isBot(userId)) {
return;

View File

@ -741,6 +741,10 @@ namespace RichTextProcessor {
return wrapEmojiText(first + last);
}
export function isUsernameValid(username: string) {
return ((username.length >= 5 && username.length <= 32) || !username.length) && /^[a-zA-Z0-9_]*$/.test(username);
}
}
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.RichTextProcessor = RichTextProcessor);

View File

@ -813,3 +813,15 @@
line-height: 1.3125;
}
}
.group-type-container {
.sidebar-left-section-caption {
font-size: .875rem;
line-height: 1rem;
margin-top: .8125rem;
}
.input-wrapper {
margin-top: .875rem;
}
}

View File

@ -1157,6 +1157,11 @@ middle-ellipsis-element {
&-subtitle {
color: var(--color-text-secondary) !important;
font-size: .875rem !important;
// * lol
line-height: 1rem;
margin-top: .1875rem;
margin-bottom: .125rem;
}
}