Group type tab
This commit is contained in:
parent
692aa02c54
commit
a4821aa08e
@ -161,4 +161,4 @@ export default class AvatarElement extends HTMLElement {
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("avatar-element", AvatarElement);
|
||||
customElements.define("avatar-element", AvatarElement);
|
||||
|
@ -27,4 +27,4 @@ const Button = (className: string, options: Partial<{noRipple: true, onlyMobile:
|
||||
return button;
|
||||
};
|
||||
|
||||
export default Button;
|
||||
export default Button;
|
||||
|
@ -5,4 +5,4 @@ const ButtonIcon = (className: string, options: Partial<{noRipple: true, onlyMob
|
||||
return button;
|
||||
};
|
||||
|
||||
export default ButtonIcon;
|
||||
export default ButtonIcon;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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, '');
|
||||
|
@ -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');
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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',
|
||||
});
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -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);
|
||||
|
148
src/components/sidebarRight/tabs/groupType.ts
Normal file
148
src/components/sidebarRight/tabs/groupType.ts
Normal 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);
|
||||
}
|
||||
}
|
98
src/components/usernameInputField.ts
Normal file
98
src/components/usernameInputField.ts
Normal 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);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user