diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index c591d0ef..b2e161ab 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -11,7 +11,7 @@ import apiManager from "../../lib/mtproto/mtprotoworker"; import opusDecodeController from "../../lib/opusDecodeController"; import { RichTextProcessor } from "../../lib/richtextprocessor"; import $rootScope from '../../lib/rootScope'; -import { cancelEvent, findUpClassName, getRichValue } from "../../helpers/dom"; +import { cancelEvent, findUpClassName, getRichValue, isInputEmpty, serializeNodes } from "../../helpers/dom"; import ButtonMenu, { ButtonMenuItemOptions } from '../buttonMenu'; import emoticonsDropdown from "../emoticonsDropdown"; import PopupCreatePoll from "../popupCreatePoll"; @@ -21,7 +21,7 @@ import { ripple } from '../ripple'; import Scrollable from "../scrollable"; import { toast } from "../toast"; import { wrapReply } from "../wrappers"; -import { checkRTL } from '../../helpers/string'; +import InputField from '../inputField'; const RECORD_MIN_TIME = 500; const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.'; @@ -30,7 +30,7 @@ type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply'; export class ChatInput { public pageEl = document.getElementById('page-chats') as HTMLDivElement; - public messageInput = document.getElementById('input-message') as HTMLDivElement/* HTMLInputElement */; + public messageInput: HTMLDivElement/* HTMLInputElement */; public fileInput = document.getElementById('input-file') as HTMLInputElement; public inputMessageContainer = document.getElementsByClassName('input-message-container')[0] as HTMLDivElement; public inputScroll = new Scrollable(this.inputMessageContainer); @@ -73,6 +73,15 @@ export class ChatInput { private helperFunc: () => void; constructor() { + const messageInputField = InputField({ + placeholder: 'Message', + name: 'message' + }); + + messageInputField.input.className = ''; + this.inputScroll.container.append(messageInputField.input); + this.messageInput = messageInputField.input; + this.attachMenu = document.getElementById('attach-file') as HTMLButtonElement; let willAttachType: 'document' | 'media'; @@ -183,8 +192,6 @@ export class ChatInput { this.messageInput.addEventListener('input', (e) => { //console.log('messageInput input', this.messageInput.innerText, this.serializeNodes(Array.from(this.messageInput.childNodes))); - this.setDirection(); - const value = this.messageInput.innerText; const entities = RichTextProcessor.parseEntities(value); @@ -217,7 +224,7 @@ export class ChatInput { } } - if(!value.trim() && !this.serializeNodes(Array.from(this.messageInput.childNodes)).trim()) { + if(!value.trim() && !serializeNodes(Array.from(this.messageInput.childNodes)).trim()) { this.messageInput.innerHTML = ''; appMessagesManager.setTyping($rootScope.selectedPeerID, 'sendMessageCancelAction'); @@ -232,7 +239,7 @@ export class ChatInput { this.updateSendBtn(); }); - if(!RichTextProcessor.emojiSupported) { + /* if(!RichTextProcessor.emojiSupported) { this.messageInput.addEventListener('copy', (e) => { const selection = document.getSelection(); @@ -243,7 +250,7 @@ export class ChatInput { let selectedNodes = Array.from(ancestorContainer.childNodes).slice(range.startOffset, range.endOffset); if(selectedNodes.length) { - str = this.serializeNodes(selectedNodes); + str = serializeNodes(selectedNodes); } else { str = selection.toString(); } @@ -257,30 +264,7 @@ export class ChatInput { event.clipboardData.setData('text/plain', str); event.preventDefault(); }); - } - - this.messageInput.addEventListener('paste', (e) => { - //console.log('messageInput paste'); - - e.preventDefault(); - // @ts-ignore - let text = (e.originalEvent || e).clipboardData.getData('text/plain'); - - let entities = RichTextProcessor.parseEntities(text); - //console.log('messageInput paste', text, entities); - entities = entities.filter(e => e._ == 'messageEntityEmoji' || e._ == 'messageEntityLinebreak'); - //text = RichTextProcessor.wrapEmojiText(text); - text = RichTextProcessor.wrapRichText(text, {entities, noLinks: true}); - - // console.log('messageInput paste after', text); - - // @ts-ignore - //let html = (e.originalEvent || e).clipboardData.getData('text/html'); - - // @ts-ignore - //console.log('paste text', text, ); - window.document.execCommand('insertHTML', false, text); - }); + } */ this.fileInput.addEventListener('change', (e) => { let files = (e.target as HTMLInputElement & EventTarget).files; @@ -292,7 +276,7 @@ export class ChatInput { this.fileInput.value = ''; }, false); - document.addEventListener('paste', (event) => { + document.addEventListener('paste', (e) => { const peerID = $rootScope.selectedPeerID; if(!peerID || $rootScope.overlayIsActive || (peerID < 0 && !appChatsManager.hasRights(peerID, 'send', 'send_media'))) { return; @@ -301,13 +285,15 @@ export class ChatInput { //console.log('document paste'); // @ts-ignore - var items = (event.clipboardData || event.originalEvent.clipboardData).items; + const items = (e.clipboardData || e.originalEvent.clipboardData).items; //console.log('item', event.clipboardData.getData()); + let foundFile = false; for(let i = 0; i < items.length; ++i) { if(items[i].kind == 'file') { - event.preventDefault() - event.cancelBubble = true; - event.stopPropagation(); + e.preventDefault() + e.cancelBubble = true; + e.stopPropagation(); + foundFile = true; let file = items[i].getAsFile(); //console.log(items[i], file); @@ -541,10 +527,8 @@ export class ChatInput { }); } - private isInputEmpty() { - let value = this.messageInput.innerText; - - return !value.trim() && !this.serializeNodes(Array.from(this.messageInput.childNodes)).trim(); + public isInputEmpty() { + return isInputEmpty(this.messageInput); } public updateSendBtn() { @@ -556,18 +540,6 @@ export class ChatInput { this.btnSend.classList.toggle('send', icon == 'send'); this.btnSend.classList.toggle('record', icon == 'record'); } - - public serializeNodes(nodes: Node[]): string { - return nodes.reduce((str, child: any) => { - //console.log('childNode', str, child, typeof(child), typeof(child) === 'string', child.innerText); - - if(typeof(child) === 'object' && child.textContent) return str += child.textContent; - if(child.innerText) return str += child.innerText; - if(child.tagName == 'IMG' && child.classList && child.classList.contains('emoji')) return str += child.getAttribute('alt'); - - return str; - }, ''); - }; public onMessageSent(clearInput = true, clearReply?: boolean) { let dialog = appMessagesManager.getDialogByPeerID(appImManager.peerID)[0]; @@ -587,17 +559,6 @@ export class ChatInput { } this.updateSendBtn(); - this.setDirection(); - } - - public setDirection() { - const char = this.messageInput.innerText[0]; - let direction = 'ltr'; - if(char && checkRTL(char)) { - direction = 'rtl'; - } - - this.messageInput.style.direction = direction; } public sendMessage() { @@ -708,7 +669,6 @@ export class ChatInput { this.editMsgID = 0; this.helperType = this.helperFunc = undefined; this.chatInput.parentElement.classList.remove('is-helper-active'); - this.setDirection(); } public setTopInfo(type: ChatInputHelperType, callerFunc: () => void, title = '', subtitle = '', input?: string, message?: any) { @@ -735,7 +695,6 @@ export class ChatInput { setTimeout(() => { this.updateSendBtn(); - this.setDirection(); }, 0); } diff --git a/src/components/inputField.ts b/src/components/inputField.ts index eb794e11..300a2218 100644 --- a/src/components/inputField.ts +++ b/src/components/inputField.ts @@ -1,33 +1,119 @@ -const InputField = (placeholder: string, label: string, name: string, maxLength?: number, showLengthOn: number = maxLength ? maxLength / 3 : 0) => { +import { getRichValue, isInputEmpty } from "../helpers/dom"; +import { checkRTL } from "../helpers/string"; +import RichTextProcessor from "../lib/richtextprocessor"; + +let init = () => { + document.addEventListener('paste', (e) => { + if(!(e.target as HTMLElement).hasAttribute('contenteditable') && !(e.target as HTMLElement).parentElement.hasAttribute('contenteditable')) { + return; + } + //console.log('document paste'); + + //console.log('messageInput paste'); + + e.preventDefault(); + // @ts-ignore + let text = (e.originalEvent || e).clipboardData.getData('text/plain'); + + let entities = RichTextProcessor.parseEntities(text); + //console.log('messageInput paste', text, entities); + entities = entities.filter(e => e._ == 'messageEntityEmoji' || e._ == 'messageEntityLinebreak'); + //text = RichTextProcessor.wrapEmojiText(text); + text = RichTextProcessor.wrapRichText(text, {entities, noLinks: true}); + + // console.log('messageInput paste after', text); + + // @ts-ignore + //let html = (e.originalEvent || e).clipboardData.getData('text/html'); + + // @ts-ignore + //console.log('paste text', text, ); + window.document.execCommand('insertHTML', false, text); + }); + + init = null; +}; + +const InputField = (options: { + placeholder?: string, + label?: string, + name: string, + maxLength?: number, + showLengthOn?: number, + plainText?: true +}) => { const div = document.createElement('div'); div.classList.add('input-field'); - div.innerHTML = ` - - - `; + if(options.maxLength) { + options.showLengthOn = Math.round(options.maxLength / 3); + } + + const {placeholder, label, maxLength, showLengthOn, name, plainText} = options; + + if(!plainText) { + if(init) { + init(); + } + div.innerHTML = ` +
+ ${label ? `` : ''} + `; + + const input = div.firstElementChild as HTMLElement; + const observer = new MutationObserver((mutationsList, observer) => { + const isEmpty = isInputEmpty(input); + console.log('input', isEmpty); + + const char = input.innerText[0]; + let direction = 'ltr'; + if(char && checkRTL(char)) { + direction = 'rtl'; + } + + input.style.direction = direction; + + if(processInput) { + processInput(); + } + }); + + // ! childList for paste first symbol + observer.observe(input, {characterData: true, childList: true, subtree: true}); + } else { + div.innerHTML = ` + + ${label ? `` : ''} + `; + } + + let processInput: () => void; if(maxLength) { const input = div.firstElementChild as HTMLInputElement; const labelEl = div.lastElementChild as HTMLLabelElement; let showingLength = false; - input.addEventListener('input', (e) => { + + processInput = () => { const wasError = input.classList.contains('error'); - const diff = maxLength - input.value.length; + const inputLength = plainText ? input.value.length : getRichValue(input).length; + const diff = maxLength - inputLength; const isError = diff < 0; input.classList.toggle('error', isError); if(isError || diff <= showLengthOn) { - labelEl.innerText = label + ` (${maxLength - input.value.length})`; + labelEl.innerText = label + ` (${maxLength - inputLength})`; if(!showingLength) showingLength = true; } else if((wasError && !isError) || showingLength) { labelEl.innerText = label; showingLength = false; } - }); + }; + + input.addEventListener('input', processInput); } - return div; + return {container: div, input: div.firstElementChild as HTMLInputElement}; }; export default InputField; \ No newline at end of file diff --git a/src/components/popupCreatePoll.ts b/src/components/popupCreatePoll.ts index 0afefbcc..92b77690 100644 --- a/src/components/popupCreatePoll.ts +++ b/src/components/popupCreatePoll.ts @@ -2,7 +2,7 @@ import appMessagesManager from "../lib/appManagers/appMessagesManager"; import appPeersManager from "../lib/appManagers/appPeersManager"; import appPollsManager, { Poll } from "../lib/appManagers/appPollsManager"; import $rootScope from "../lib/rootScope"; -import { findUpTag, whichChild } from "../helpers/dom"; +import { cancelEvent, findUpTag, getRichValue, isInputEmpty, whichChild } from "../helpers/dom"; import CheckboxField from "./checkbox"; import InputField from "./inputField"; import { PopupElement } from "./popup"; @@ -32,10 +32,15 @@ export default class PopupCreatePoll extends PopupElement { this.title.innerText = 'New Poll'; - const questionField = InputField('Ask a Question', 'Ask a Question', 'question', MAX_LENGTH_QUESTION); - this.questionInput = questionField.firstElementChild as HTMLInputElement; + const questionField = InputField({ + placeholder: 'Ask a Question', + label: 'Ask a Question', + name: 'question', + maxLength: MAX_LENGTH_QUESTION + }); + this.questionInput = questionField.input; - this.header.append(questionField); + this.header.append(questionField.container); const hr = document.createElement('hr'); const d = document.createElement('div'); @@ -93,14 +98,19 @@ export default class PopupCreatePoll extends PopupElement { const quizSolutionContainer = document.createElement('div'); quizSolutionContainer.classList.add('poll-create-questions'); - const quizSolutionField = InputField('Add a Comment (Optional)', 'Add a Comment (Optional)', 'solution', MAX_LENGTH_SOLUTION); - this.quizSolutionInput = quizSolutionField.firstElementChild as HTMLInputElement; + const quizSolutionField = InputField({ + placeholder: 'Add a Comment (Optional)', + label: 'Add a Comment (Optional)', + name: 'solution', + maxLength: MAX_LENGTH_SOLUTION + }); + this.quizSolutionInput = quizSolutionField.input; const quizSolutionSubtitle = document.createElement('div'); quizSolutionSubtitle.classList.add('subtitle'); quizSolutionSubtitle.innerText = 'Users will see this comment after choosing a wrong answer, good for educational purposes.'; - quizSolutionContainer.append(quizSolutionField, quizSolutionSubtitle); + quizSolutionContainer.append(quizSolutionField.container, quizSolutionSubtitle); quizElements.push(quizHr, quizSolutionCaption, quizSolutionContainer); quizElements.forEach(el => el.classList.add('hide')); @@ -120,15 +130,15 @@ export default class PopupCreatePoll extends PopupElement { private getFilledAnswers() { const answers = Array.from(this.questions.children).map((el, idx) => { - const input = el.querySelector('input[type="text"]') as HTMLInputElement; - return input.value; + const input = el.querySelector('.input-field-input'); + return getRichValue(input); }).filter(v => !!v.trim()); return answers; } onSubmitClick = (e: MouseEvent) => { - const question = this.questionInput.value.trim(); + const question = getRichValue(this.questionInput); if(!question) { toast('Please enter a question.'); @@ -158,7 +168,7 @@ export default class PopupCreatePoll extends PopupElement { return; } - const quizSolution = this.quizSolutionInput.value.trim() || undefined; + const quizSolution = getRichValue(this.quizSolutionInput) || undefined; if(quizSolution?.length > MAX_LENGTH_SOLUTION) { toast('Explanation is too long.'); return; @@ -210,14 +220,15 @@ export default class PopupCreatePoll extends PopupElement { const target = e.target as HTMLInputElement; const radioLabel = findUpTag(target, 'LABEL'); - if(target.value.length) { + const isEmpty = isInputEmpty(target); + if(!isEmpty) { target.parentElement.classList.add('is-filled'); radioLabel.classList.remove('hidden-widget'); radioLabel.firstElementChild.removeAttribute('disabled'); } const isLast = !radioLabel.nextElementSibling; - if(isLast && target.value.length && this.questions.childElementCount < 10) { + if(isLast && !isEmpty && this.questions.childElementCount < 10) { this.appendMoreField(); } }; @@ -235,11 +246,17 @@ export default class PopupCreatePoll extends PopupElement { private appendMoreField() { const tempID = this.tempID++; const idx = this.questions.childElementCount + 1; - const questionField = InputField('Add an Option', 'Option ' + idx, 'question-' + tempID, MAX_LENGTH_OPTION); - (questionField.firstElementChild as HTMLInputElement).addEventListener('input', this.onInput); + const questionField = InputField({ + placeholder: 'Add an Option', + label: 'Option ' + idx, + name: 'question-' + tempID, + maxLength: MAX_LENGTH_OPTION + }); + questionField.input.addEventListener('input', this.onInput); const radioField = RadioField('', 'question'); - radioField.main.append(questionField); + radioField.main.append(questionField.container); + radioField.main.addEventListener('click', cancelEvent); radioField.label.classList.add('hidden-widget'); radioField.input.disabled = true; if(!this.quizCheckboxField.input.checked) { @@ -255,7 +272,7 @@ export default class PopupCreatePoll extends PopupElement { const deleteBtn = document.createElement('span'); deleteBtn.classList.add('btn-icon', 'tgico-close'); - questionField.append(deleteBtn); + questionField.container.append(deleteBtn); deleteBtn.addEventListener('click', this.onDeleteClick, {once: true}); diff --git a/src/components/popupNewMedia.ts b/src/components/popupNewMedia.ts index a6e94c0f..564fe0a3 100644 --- a/src/components/popupNewMedia.ts +++ b/src/components/popupNewMedia.ts @@ -1,7 +1,7 @@ import { isTouchSupported } from "../helpers/touchSupport"; import appImManager from "../lib/appManagers/appImManager"; import appMessagesManager from "../lib/appManagers/appMessagesManager"; -import { calcImageInBox } from "../helpers/dom"; +import { calcImageInBox, getRichValue } from "../helpers/dom"; import { Layouter, RectPart } from "./groupedLayout"; import InputField from "./inputField"; import { PopupElement } from "./popup"; @@ -53,9 +53,15 @@ export default class PopupNewMedia extends PopupElement { const scrollable = new Scrollable(null); scrollable.container.append(this.mediaContainer); - const inputField = InputField('Add a caption...', 'Caption', 'photo-caption', MAX_LENGTH_CAPTION, 80); - this.input = inputField.firstElementChild as HTMLInputElement; - this.container.append(scrollable.container, inputField); + const inputField = InputField({ + placeholder: 'Add a caption...', + label: 'Caption', + name: 'photo-caption', + maxLength: MAX_LENGTH_CAPTION, + showLengthOn: 80 + }); + this.input = inputField.input; + this.container.append(scrollable.container, inputField.container); this.attachFiles(files); } @@ -72,7 +78,7 @@ export default class PopupNewMedia extends PopupElement { }; public send = () => { - let caption = this.input.value.trim(); + let caption = getRichValue(this.input); if(caption.length > MAX_LENGTH_CAPTION) { toast('Caption is too long.'); return; diff --git a/src/components/scrollable.ts b/src/components/scrollable.ts index d2aed243..df24d912 100644 --- a/src/components/scrollable.ts +++ b/src/components/scrollable.ts @@ -158,7 +158,7 @@ export default class Scrollable extends ScrollableBase { }); }; - public checkForTriggers() { + public checkForTriggers = () => { if(this.scrollLocked || (!this.onScrolledTop && !this.onScrolledBottom)) return; const container = this.container; @@ -179,7 +179,7 @@ export default class Scrollable extends ScrollableBase { if(this.onScrolledBottom && (maxScrollTop - scrollTop) <= this.onScrollOffset) { this.onScrolledBottom(); } - } + }; public prepend(element: HTMLElement) { (this.splitUp || this.container).prepend(element); diff --git a/src/components/sidebarLeft/tabs/archivedTab.ts b/src/components/sidebarLeft/tabs/archivedTab.ts index e6e3d832..fcc21d6c 100644 --- a/src/components/sidebarLeft/tabs/archivedTab.ts +++ b/src/components/sidebarLeft/tabs/archivedTab.ts @@ -19,7 +19,7 @@ export default class AppArchivedTab implements SliderTab { appDialogsManager.setListClickListener(this.chatList, null, true); window.addEventListener('resize', () => { - setTimeout(appDialogsManager.onChatsScroll, 0); + setTimeout(appDialogsManager.scroll.checkForTriggers, 0); }); } diff --git a/src/components/sidebarLeft/tabs/editProfile.ts b/src/components/sidebarLeft/tabs/editProfile.ts index 500bde25..e9e19fac 100644 --- a/src/components/sidebarLeft/tabs/editProfile.ts +++ b/src/components/sidebarLeft/tabs/editProfile.ts @@ -1,9 +1,13 @@ import appSidebarLeft from ".."; +import { getRichValue } from "../../../helpers/dom"; import { InputFile } from "../../../layer"; import appProfileManager from "../../../lib/appManagers/appProfileManager"; import appUsersManager from "../../../lib/appManagers/appUsersManager"; import apiManager from "../../../lib/mtproto/mtprotoworker"; +import RichTextProcessor from "../../../lib/richtextprocessor"; import $rootScope from "../../../lib/rootScope"; +import AvatarElement from "../../avatar"; +import InputField from "../../inputField"; import PopupAvatar from "../../popupAvatar"; import Scrollable from "../../scrollable"; import { SliderTab } from "../../slider"; @@ -11,21 +15,21 @@ import { SliderTab } from "../../slider"; // TODO: аватарка не поменяется в этой вкладке после изменения почему-то (если поставить в другом клиенте, и потом тут проверить, для этого ещё вышел в чатлист) export default class AppEditProfileTab implements SliderTab { - private container = document.querySelector('.edit-profile-container') as HTMLDivElement; - private scrollWrapper = this.container.querySelector('.scroll-wrapper') as HTMLDivElement; - private nextBtn = this.container.querySelector('.btn-corner') as HTMLButtonElement; - private canvas = this.container.querySelector('.avatar-edit-canvas') as HTMLCanvasElement; + private container: HTMLElement; + private scrollWrapper: HTMLElement; + private nextBtn: HTMLButtonElement; + private canvas: HTMLCanvasElement; private uploadAvatar: () => Promise = null; - private firstNameInput = this.container.querySelector('.firstname') as HTMLInputElement; - private lastNameInput = this.container.querySelector('.lastname') as HTMLInputElement; - private bioInput = this.container.querySelector('.bio') as HTMLInputElement; - private userNameInput = this.container.querySelector('.username') as HTMLInputElement; + private firstNameInput: HTMLInputElement; + private lastNameInput: HTMLInputElement; + private bioInput: HTMLInputElement; + private userNameInput: HTMLInputElement; - private avatarElem = document.createElement('avatar-element'); + private avatarElem: AvatarElement; - private profileUrlContainer = this.container.querySelector('.profile-url-container') as HTMLDivElement; - private profileUrlAnchor = this.profileUrlContainer.lastElementChild as HTMLAnchorElement; + private profileUrlContainer: HTMLDivElement; + private profileUrlAnchor: HTMLAnchorElement; private originalValues = { firstName: '', @@ -34,8 +38,20 @@ export default class AppEditProfileTab implements SliderTab { bio: '' }; - constructor() { - this.container.querySelector('.avatar-edit').addEventListener('click', () => { + public init() { + this.container = document.querySelector('.edit-profile-container'); + this.scrollWrapper = this.container.querySelector('.scroll-wrapper'); + this.nextBtn = this.container.querySelector('.btn-corner'); + this.canvas = this.container.querySelector('.avatar-edit-canvas'); + + this.avatarElem = document.createElement('avatar-element') as AvatarElement; + this.avatarElem.classList.add('avatar-placeholder'); + + this.profileUrlContainer = this.container.querySelector('.profile-url-container'); + this.profileUrlAnchor = this.profileUrlContainer.lastElementChild as HTMLAnchorElement; + + const avatarEdit = this.container.querySelector('.avatar-edit'); + avatarEdit.addEventListener('click', () => { new PopupAvatar().open(this.canvas, (_upload) => { this.uploadAvatar = _upload; this.handleChange(); @@ -43,13 +59,56 @@ export default class AppEditProfileTab implements SliderTab { }); }); - this.avatarElem.classList.add('avatar-placeholder'); + { + const inputWrapper = document.createElement('div'); + inputWrapper.classList.add('input-wrapper'); + + const firstNameInputField = InputField({ + label: 'Name', + name: 'first-name', + maxLength: 70 + }); + const lastNameInputField = InputField({ + label: 'Last Name', + name: 'last-name', + maxLength: 64 + }); + const bioInputField = InputField({ + label: 'Bio (optional)', + name: 'bio', + maxLength: 70 + }); + + this.firstNameInput = firstNameInputField.input; + this.lastNameInput = lastNameInputField.input; + this.bioInput = bioInputField.input; + + inputWrapper.append(firstNameInputField.container, lastNameInputField.container, bioInputField.container); + avatarEdit.parentElement.insertBefore(inputWrapper, avatarEdit.nextElementSibling); + } + + { + const inputWrapper = document.createElement('div'); + inputWrapper.classList.add('input-wrapper'); + + const userNameInputField = InputField({ + label: 'Username (optional)', + name: 'username', + plainText: true + }); + this.userNameInput = userNameInputField.input; + + inputWrapper.append(userNameInputField.container); + + const caption = this.profileUrlContainer.parentElement; + caption.parentElement.insertBefore(inputWrapper, caption); + } let userNameLabel = this.userNameInput.nextElementSibling as HTMLLabelElement; - this.firstNameInput.addEventListener('input', () => this.handleChange()); - this.lastNameInput.addEventListener('input', () => this.handleChange()); - this.bioInput.addEventListener('input', () => this.handleChange()); + this.firstNameInput.addEventListener('input', this.handleChange); + this.lastNameInput.addEventListener('input', this.handleChange); + this.bioInput.addEventListener('input', this.handleChange); this.userNameInput.addEventListener('input', () => { let value = this.userNameInput.value; @@ -110,8 +169,7 @@ export default class AppEditProfileTab implements SliderTab { let promises: Promise[] = []; - - promises.push(appProfileManager.updateProfile(this.firstNameInput.value, this.lastNameInput.value, this.bioInput.value).then(() => { + promises.push(appProfileManager.updateProfile(getRichValue(this.firstNameInput), getRichValue(this.lastNameInput), getRichValue(this.bioInput)).then(() => { appSidebarLeft.selectTab(0); }, (err) => { console.error('updateProfile error:', err); @@ -127,10 +185,8 @@ export default class AppEditProfileTab implements SliderTab { promises.push(appProfileManager.updateUsername(this.userNameInput.value)); } - Promise.race(promises).then(() => { - this.nextBtn.disabled = false; - }, () => { - this.nextBtn.disabled = false; + Promise.race(promises).finally(() => { + this.nextBtn.removeAttribute('disabled'); }); }); @@ -138,17 +194,34 @@ export default class AppEditProfileTab implements SliderTab { } public fillElements() { - let user = appUsersManager.getSelf(); - this.firstNameInput.value = this.originalValues.firstName = user.first_name ?? ''; - this.lastNameInput.value = this.originalValues.lastName = user.last_name ?? ''; + if(this.init) { + this.init(); + this.init = null; + } + + const user = appUsersManager.getSelf(); + + Object.assign(this.originalValues, { + firstName: user.first_name, + lastName: user.last_name, + userName: user.username + }); + + this.firstNameInput.innerHTML = user.rFirstName; + this.lastNameInput.innerHTML = RichTextProcessor.wrapRichText(user.last_name, {noLinks: true, noLinebreaks: true}); this.userNameInput.value = this.originalValues.userName = user.username ?? ''; + this.firstNameInput.classList.remove('error'); + this.lastNameInput.classList.remove('error'); + this.bioInput.classList.remove('error'); + this.userNameInput.classList.remove('valid', 'error'); this.userNameInput.nextElementSibling.innerHTML = 'Username (optional)'; - appProfileManager.getProfile(user.id).then(userFull => { + appProfileManager.getProfile(user.id, true).then(userFull => { if(userFull.rAbout) { - this.bioInput.value = this.originalValues.bio = userFull.rAbout; + this.originalValues.bio = userFull.about; + this.bioInput.innerHTML = userFull.rAbout; } }); @@ -168,10 +241,10 @@ export default class AppEditProfileTab implements SliderTab { private isChanged() { return !!this.uploadAvatar - || this.firstNameInput.value != this.originalValues.firstName - || this.lastNameInput.value != this.originalValues.lastName - || (this.userNameInput.value != this.originalValues.userName && !this.userNameInput.classList.contains('error')) - || this.bioInput.value != this.originalValues.bio; + || (!this.firstNameInput.classList.contains('error') && getRichValue(this.firstNameInput) != this.originalValues.firstName) + || (!this.lastNameInput.classList.contains('error') && getRichValue(this.lastNameInput) != this.originalValues.lastName) + || (!this.bioInput.classList.contains('error') && getRichValue(this.bioInput) != this.originalValues.bio) + || (this.userNameInput.value != this.originalValues.userName && !this.userNameInput.classList.contains('error')); } private setProfileUrl() { @@ -185,13 +258,13 @@ export default class AppEditProfileTab implements SliderTab { } } - private handleChange() { + private handleChange = () => { if(this.isChanged()) { this.nextBtn.classList.add('is-visible'); } else { this.nextBtn.classList.remove('is-visible'); } - } + }; onCloseAfterTimeout() { this.nextBtn.classList.remove('is-visible'); diff --git a/src/helpers/dom.ts b/src/helpers/dom.ts index de86a929..3ce752b8 100644 --- a/src/helpers/dom.ts +++ b/src/helpers/dom.ts @@ -127,6 +127,28 @@ export function getRichElementValue(node: any, lines: string[], line: string[], } } +export function isInputEmpty(element: HTMLElement) { + if(element.hasAttribute('contenteditable') || element.tagName != 'INPUT') { + const value = element.innerText; + + return !value.trim() && !serializeNodes(Array.from(element.childNodes)).trim(); + } else { + return !(element as HTMLInputElement).value.trim().length; + } +} + +export function serializeNodes(nodes: Node[]): string { + return nodes.reduce((str, child: any) => { + //console.log('childNode', str, child, typeof(child), typeof(child) === 'string', child.innerText); + + if(typeof(child) === 'object' && child.textContent) return str += child.textContent; + if(child.innerText) return str += child.innerText; + if(child.tagName == 'IMG' && child.classList && child.classList.contains('emoji')) return str += child.getAttribute('alt'); + + return str; + }, ''); +} + /* if (Config.Modes.animations && typeof window.requestAnimationFrame == 'function') { window.onAnimationFrameCallback = function (cb) { diff --git a/src/index.hbs b/src/index.hbs index 523c1fec..e01cdc0f 100644 --- a/src/index.hbs +++ b/src/index.hbs @@ -168,7 +168,7 @@ - + - + -
-
- - -
-
- - -
-
- - -
-
Any details such as age, occupation or city. Example:
23 y.o. designer from San Francisco.

-
-
- - -
-
You can choose a username on Telegram. If you do, other people will be able to find you by this username and contact you without knowing your phone number.

You can use a-z, 0-9 and underscores. Minimum length is 5 characters.

This link opens a chat with you:
- +