New input field:
Support emoji Support RTL Support cyrillic
This commit is contained in:
parent
b8e3ce1e4d
commit
a885492a09
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 = `
|
||||
<input type="text" name="${name}" id="input-${name}" placeholder="${placeholder}" autocomplete="off" required="">
|
||||
<label for="input-${name}">${label}</label>
|
||||
`;
|
||||
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 = `
|
||||
<div id="input-${name}" ${placeholder ? `data-placeholder="${placeholder}"` : ''} contenteditable="true" class="input-field-input"></div>
|
||||
${label ? `<label for="input-${name}">${label}</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 = `
|
||||
<input type="text" name="${name}" id="input-${name}" ${placeholder ? `placeholder="${placeholder}"` : ''} autocomplete="off" required="" class="input-field-input">
|
||||
${label ? `<label for="input-${name}">${label}</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;
|
@ -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});
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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<InputFile> = 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<any>[] = [];
|
||||
|
||||
|
||||
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');
|
||||
|
@ -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) {
|
||||
|
@ -168,7 +168,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="transition-item sidebar-search" id="search-container"></div>
|
||||
<button class="btn-primary btn-circle btn-icon rp btn-corner tgico-newchat_filled btn-menu-toggle" id="new-menu">
|
||||
<button class="btn-circle rp btn-corner tgico-newchat_filled btn-menu-toggle" id="new-menu">
|
||||
<div class="btn-menu top-left">
|
||||
<div class="btn-menu-item menu-channel tgico-newchannel rp">New Channel</div>
|
||||
<div class="btn-menu-item menu-group tgico-newgroup rp">New Group</div>
|
||||
@ -219,7 +219,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="caption">You can provide an optional description for your channel.</div>
|
||||
<button class="btn-primary btn-circle btn-icon rp btn-corner tgico-next"></button>
|
||||
<button class="btn-circle rp btn-corner tgico-next"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-slider-item addmembers-container">
|
||||
@ -228,7 +228,7 @@
|
||||
<div class="sidebar-header__title">Add Members</div>
|
||||
</div>
|
||||
<div class="sidebar-content">
|
||||
<button class="btn-primary btn-circle btn-icon rp btn-corner tgico-next"></button>
|
||||
<button class="btn-circle rp btn-corner tgico-next"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-slider-item new-group-container">
|
||||
@ -247,7 +247,7 @@
|
||||
<label for="new-group-name">Group Name</label>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-primary btn-circle btn-icon rp btn-corner tgico-next"></button>
|
||||
<button class="btn-circle rp btn-corner tgico-next"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-slider-item settings-container">
|
||||
@ -287,34 +287,14 @@
|
||||
<canvas class="avatar-edit-canvas"></canvas>
|
||||
<span class="tgico tgico-cameraadd"></span>
|
||||
</div>
|
||||
<div class="input-wrapper">
|
||||
<div class="input-field">
|
||||
<input type="text" name="name" class="firstname" autocomplete="xxDDqqOXJXC" id="settings-name" required="">
|
||||
<label for="settings-name">Name</label>
|
||||
</div>
|
||||
<div class="input-field">
|
||||
<input type="text" name="lastname" class="lastname" autocomplete="aintsofunnowzXCFF" id="settings-lastname" required="">
|
||||
<label for="settings-lastname">Last Name</label>
|
||||
</div>
|
||||
<div class="input-field">
|
||||
<input type="text" name="bio" class="bio" autocomplete="aintsofunnowhHQ" id="settings-bio" required="">
|
||||
<label for="settings-bio">Bio (optional)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="caption">Any details such as age, occupation or city. Example:<br>23 y.o. designer from San Francisco.</div>
|
||||
<hr/>
|
||||
<div class="sidebar-left-h2">Username</div>
|
||||
<div class="input-wrapper">
|
||||
<div class="input-field">
|
||||
<input type="text" name="username" class="username" autocomplete="xxDDqqOXffEER" id="settings-username" required="">
|
||||
<label for="settings-username">Username (optional)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="caption">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.<br><br>You can use a-z, 0-9 and underscores. Minimum length is 5 characters.<br><br><div class="profile-url-container">This link opens a chat with you:
|
||||
<br><a class="profile-url" href="#" target="_blank"></a></div>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-primary btn-circle btn-icon rp btn-corner tgico-check"></button>
|
||||
<button class="btn-circle rp btn-corner tgico-check"></button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-slider-item chat-folders-container">
|
||||
@ -447,9 +427,7 @@
|
||||
<div class="new-message-wrapper">
|
||||
<button class="btn-icon rp tgico toggle-emoticons" id="toggle-emoticons"></button>
|
||||
<!-- <textarea type="text" id="input-message" placeholder="Message" contenteditable="true"></textarea> -->
|
||||
<div class="input-message-container">
|
||||
<div id="input-message" contenteditable="true" data-placeholder="Message"></div>
|
||||
</div>
|
||||
<div class="input-message-container"></div>
|
||||
<button class="btn-icon tgico-attach btn-menu-toggle" id="attach-file"></button>
|
||||
<div class="record-time"></div>
|
||||
<input type="file" id="input-file" style="display: none;" multiple />
|
||||
|
@ -68,7 +68,7 @@ export class AppChatsManager {
|
||||
public chats: {[id: number]: Channel | Chat | any} = {};
|
||||
//public usernames: any = {};
|
||||
//public channelAccess: any = {};
|
||||
public megagroups: {[id: number]: true} = {};
|
||||
//public megagroups: {[id: number]: true} = {};
|
||||
public cachedPhotoLocations: {[id: number]: any} = {};
|
||||
|
||||
public megagroupOnlines: {[id: number]: {timestamp: number, onlines: number}} = {};
|
||||
@ -253,13 +253,13 @@ export class AppChatsManager {
|
||||
this.channelAccess[id] = accessHash;
|
||||
} */
|
||||
|
||||
public saveIsMegagroup(id: number) {
|
||||
/* public saveIsMegagroup(id: number) {
|
||||
this.megagroups[id] = true;
|
||||
}
|
||||
} */
|
||||
|
||||
public isChannel(id: number) {
|
||||
if(id < 0) id = -id;
|
||||
let chat = this.chats[id];
|
||||
const chat = this.chats[id];
|
||||
if(chat && (chat._ == 'channel' || chat._ == 'channelForbidden')/* || this.channelAccess[id] */) {
|
||||
return true;
|
||||
}
|
||||
@ -267,11 +267,11 @@ export class AppChatsManager {
|
||||
}
|
||||
|
||||
public isMegagroup(id: number) {
|
||||
if(this.megagroups[id]) {
|
||||
/* if(this.megagroups[id]) {
|
||||
return true;
|
||||
}
|
||||
} */
|
||||
|
||||
let chat = this.chats[id];
|
||||
const chat = this.chats[id];
|
||||
if(chat && chat._ == 'channel' && chat.pFlags.megagroup) {
|
||||
return true;
|
||||
}
|
||||
|
@ -242,7 +242,7 @@ export class AppUsersManager {
|
||||
|
||||
const fullName = user.first_name + ' ' + (user.last_name || '');
|
||||
if(user.first_name) {
|
||||
user.rFirstName = RichTextProcessor.wrapRichText(user.first_name, {noLinks: true, noLinebreaks: true})
|
||||
user.rFirstName = RichTextProcessor.wrapRichText(user.first_name, {noLinks: true, noLinebreaks: true});
|
||||
user.rFullName = user.last_name ? RichTextProcessor.wrapRichText(fullName, {noLinks: true, noLinebreaks: true}) : user.rFirstName;
|
||||
} else {
|
||||
user.rFirstName = RichTextProcessor.wrapRichText(user.last_name, {noLinks: true, noLinebreaks: true}) || user.rPhone || 'user_first_name_deleted';
|
||||
|
@ -809,15 +809,15 @@ namespace RichTextProcessor {
|
||||
let first = '', last = '';
|
||||
|
||||
const firstNode = childNodes[0];
|
||||
if('length' in firstNode) first = (firstNode as any).textContent.charAt(0).toUpperCase();
|
||||
if('length' in firstNode) first = (firstNode as any).textContent.trim().charAt(0).toUpperCase();
|
||||
else first = (firstNode as HTMLElement).outerHTML;
|
||||
|
||||
if(onlyFirst) return first;
|
||||
|
||||
if(str.indexOf(' ') !== -1) {
|
||||
const lastNode = childNodes[childNodes.length - 1];
|
||||
if(lastNode == firstNode) last = lastNode.textContent.split(' ').pop().charAt(0).toUpperCase();
|
||||
else if('length' in lastNode) last = (lastNode as any).textContent.charAt(0).toUpperCase();
|
||||
if(lastNode == firstNode) last = lastNode.textContent.split(' ').pop().trim().charAt(0).toUpperCase();
|
||||
else if('length' in lastNode) last = (lastNode as any).textContent.trim().charAt(0).toUpperCase();
|
||||
else last = (lastNode as HTMLElement).outerHTML;
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,18 @@
|
||||
transform: translate3d(0, var(--translateY), 0);
|
||||
z-index: 3;
|
||||
user-select: none;
|
||||
background-color: $color-blue;
|
||||
text-align: center;
|
||||
font-size: 1.5rem;
|
||||
color: #fff;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
overflow: hidden;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&.is-visible {
|
||||
--translateY: 0;
|
||||
@ -49,7 +61,12 @@
|
||||
|
||||
body.animation-level-0 & {
|
||||
transition: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
opacity: 1 !important;
|
||||
pointer-events: all !important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-menu {
|
||||
@ -238,7 +255,7 @@
|
||||
cursor: pointer !important;
|
||||
pointer-events: all !important;
|
||||
|
||||
&:not(.btn-primary).menu-open {
|
||||
&:not(.btn-primary):not(.btn-corner).menu-open {
|
||||
background-color: var(--color-gray-hover);
|
||||
}
|
||||
|
||||
|
@ -290,7 +290,6 @@ $chat-helper-size: 39px;
|
||||
resize: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: text;
|
||||
|
||||
@media only screen and (max-height: 30rem) {
|
||||
max-height: unquote('max(39px, calc(100vh - 10rem))');
|
||||
@ -303,13 +302,6 @@ $chat-helper-size: 39px;
|
||||
/* span.emoji {
|
||||
font-size: .95rem;
|
||||
} */
|
||||
|
||||
//[contenteditable=true]:empty:before {
|
||||
&:empty:before {
|
||||
content: attr(data-placeholder);
|
||||
color: #a2acb4;
|
||||
display: block; /* For Firefox By Ariel Flesler */
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-emoticons {
|
||||
@ -967,7 +959,6 @@ $chat-helper-size: 39px;
|
||||
box-shadow: 0 1px 2px 0 rgba(16, 35, 47, .07);
|
||||
min-height: $chat-input-size;
|
||||
max-height: 30rem;
|
||||
caret-color: $button-primary-background;
|
||||
flex: 0 0 auto;
|
||||
position: relative;
|
||||
z-index: 3;
|
||||
|
155
src/scss/partials/_input.scss
Normal file
155
src/scss/partials/_input.scss
Normal file
@ -0,0 +1,155 @@
|
||||
.input-wrapper {
|
||||
width: 360px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
position: relative;
|
||||
|
||||
.arrow-down {
|
||||
position: absolute;
|
||||
content: " ";
|
||||
top: 50%;
|
||||
bottom: 0;
|
||||
right: 21px;
|
||||
cursor: pointer;
|
||||
|
||||
height: 0;
|
||||
width: 0;
|
||||
|
||||
border: solid #707579;
|
||||
border-radius: 1px;
|
||||
border-width: 0 2px 2px 0;
|
||||
display: inline-block;
|
||||
padding: 5px;
|
||||
vertical-align: middle;
|
||||
|
||||
z-index: 2;
|
||||
|
||||
margin-top: -9px;
|
||||
transform: rotate(45deg);
|
||||
-webkit-transform: rotate(45deg);
|
||||
transition: .2s all;
|
||||
}
|
||||
|
||||
label {
|
||||
position: absolute;
|
||||
color: $placeholder-color;
|
||||
left: 1rem;
|
||||
right: auto;
|
||||
z-index: 2;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background-color: #fff;
|
||||
transition: .2s all, .1s opacity;
|
||||
display: inline-block;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
input, &-input {
|
||||
--border-width: 1px;
|
||||
--border-width-top: 2px;
|
||||
border: var(--border-width) solid #DADCE0;
|
||||
border-radius: $border-radius-medium;
|
||||
//padding: 0 1rem;
|
||||
padding: calc(1rem - var(--border-width-top)) calc(1rem - var(--border-width));
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
min-height: 54px;
|
||||
transition: .2s border-color;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
//line-height: calc(54px - var(--border-width));
|
||||
/* overflow: hidden;
|
||||
white-space: nowrap; */
|
||||
|
||||
html.no-touch & {
|
||||
&:hover:not(:focus):not(.error):not(.valid) {
|
||||
border-color: var(--color-gray);
|
||||
}
|
||||
}
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
height: 50px;
|
||||
}
|
||||
/* font-weight: 500; */
|
||||
|
||||
/* &:hover {
|
||||
border-color: #000;
|
||||
} */
|
||||
|
||||
&:focus {
|
||||
--border-width: 2px;
|
||||
--border-width-top: 3px;
|
||||
border-color: $button-primary-background;
|
||||
//padding: 0 calc(1rem - 1px);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-color: $color-error;
|
||||
|
||||
& + label {
|
||||
color: $color-error!important;
|
||||
}
|
||||
}
|
||||
|
||||
&.valid {
|
||||
border-color: #26962F;
|
||||
|
||||
& + label {
|
||||
color: #26962F !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* &.error, &.valid {
|
||||
transition: .2s border-width;
|
||||
} */
|
||||
|
||||
&:focus ~ .arrow-down {
|
||||
margin-top: -4px;
|
||||
transform: rotate(225deg);
|
||||
-webkit-transform: rotate(225deg);
|
||||
border-color: $button-primary-background;
|
||||
}
|
||||
|
||||
&:focus + label {
|
||||
color: $button-primary-background;
|
||||
}
|
||||
|
||||
&:focus + label, &:valid + label, &:not(:empty) + label, &:disabled + label {
|
||||
top: -.5rem;
|
||||
transform: none;
|
||||
padding: 0 5px;
|
||||
left: .75rem;
|
||||
font-size: 0.75rem!important;
|
||||
//color: #666;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-wrapper > * + * {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
|
||||
color: #909192;
|
||||
opacity: 1; /* Firefox */
|
||||
}
|
||||
|
||||
:-ms-input-placeholder { /* Internet Explorer 10-11 */
|
||||
color: #a2acb4;
|
||||
}
|
||||
|
||||
::-ms-input-placeholder { /* Microsoft Edge */
|
||||
color: #a2acb4;
|
||||
}
|
||||
|
||||
input:focus, button:focus {
|
||||
outline: none;
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
justify-content: center;
|
||||
|
||||
h4 {
|
||||
&[contenteditable="true"] {
|
||||
&[contenteditable] {
|
||||
padding: 0 1rem;
|
||||
border: none;
|
||||
outline: none;
|
||||
|
@ -25,9 +25,8 @@
|
||||
.btn-icon {
|
||||
position: absolute;
|
||||
right: .5rem;
|
||||
top: 50%;
|
||||
top: .5rem;
|
||||
z-index: 1;
|
||||
transform: translateY(-50%);
|
||||
opacity: 1;
|
||||
transition: opacity .2s ease;
|
||||
}
|
||||
@ -47,7 +46,7 @@
|
||||
.poll-create-questions {
|
||||
padding: 0px 1.25rem 2.03125rem;
|
||||
|
||||
.input-field input {
|
||||
.input-field-input {
|
||||
padding-right: 3.25rem;
|
||||
}
|
||||
}
|
||||
@ -65,4 +64,8 @@
|
||||
font-size: .875rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
width: 94px;
|
||||
}
|
||||
}
|
@ -132,27 +132,22 @@
|
||||
margin-top: 25px;
|
||||
flex: 0 0 auto;
|
||||
|
||||
&::placeholder {
|
||||
color: #a2acb4;
|
||||
}
|
||||
|
||||
input {
|
||||
&-input {
|
||||
//height: 54px;
|
||||
font-size: 1rem;
|
||||
border-radius: $border-radius-medium;
|
||||
|
||||
&:not(:focus):empty + label {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: inherit;
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup-create-poll.popup-new-media .btn-primary {
|
||||
width: 94px;
|
||||
}
|
||||
|
||||
.popup-new-media.popup-send-photo {
|
||||
.popup-header {
|
||||
padding: 0;
|
||||
|
@ -102,6 +102,7 @@ $messages-container-width: 728px;
|
||||
}
|
||||
|
||||
@import "partials/ico";
|
||||
@import "partials/input";
|
||||
@import "partials/button";
|
||||
@import "partials/checkbox";
|
||||
@import "partials/chatlist";
|
||||
@ -294,7 +295,7 @@ h4 {
|
||||
}
|
||||
}
|
||||
|
||||
input {
|
||||
input, [contenteditable] {
|
||||
caret-color: $button-primary-background;
|
||||
}
|
||||
|
||||
@ -442,140 +443,6 @@ hr {
|
||||
}
|
||||
}
|
||||
|
||||
.input-wrapper {
|
||||
width: 360px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
position: relative;
|
||||
|
||||
.arrow-down {
|
||||
position: absolute;
|
||||
content: " ";
|
||||
top: 50%;
|
||||
bottom: 0;
|
||||
right: 21px;
|
||||
cursor: pointer;
|
||||
|
||||
height: 0;
|
||||
width: 0;
|
||||
|
||||
border: solid #707579;
|
||||
border-radius: 1px;
|
||||
border-width: 0 2px 2px 0;
|
||||
display: inline-block;
|
||||
padding: 5px;
|
||||
vertical-align: middle;
|
||||
|
||||
z-index: 2;
|
||||
|
||||
margin-top: -9px;
|
||||
transform: rotate(45deg);
|
||||
-webkit-transform: rotate(45deg);
|
||||
transition: .2s all;
|
||||
}
|
||||
|
||||
label {
|
||||
position: absolute;
|
||||
color: $placeholder-color;
|
||||
left: 1rem;
|
||||
right: auto;
|
||||
z-index: 2;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background-color: #fff;
|
||||
transition: .2s all, .1s opacity;
|
||||
display: inline-block;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
input {
|
||||
--border-width: 1px;
|
||||
border: var(--border-width) solid #DADCE0;
|
||||
border-radius: $border-radius-medium;
|
||||
//padding: 0 1rem;
|
||||
padding: 0 calc(1rem - var(--border-width));
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
height: 54px;
|
||||
transition: .2s border-color;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
html.no-touch & {
|
||||
&:hover:not(:focus):not(.error):not(.valid) {
|
||||
border-color: var(--color-gray);
|
||||
}
|
||||
}
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
height: 50px;
|
||||
}
|
||||
/* font-weight: 500; */
|
||||
|
||||
/* &:hover {
|
||||
border-color: #000;
|
||||
} */
|
||||
|
||||
&:focus {
|
||||
--border-width: 2px;
|
||||
border-color: $button-primary-background;
|
||||
//padding: 0 calc(1rem - 1px);
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: #fff;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
&.error {
|
||||
border-color: $color-error;
|
||||
|
||||
& + label {
|
||||
color: $color-error!important;
|
||||
}
|
||||
}
|
||||
|
||||
&.valid {
|
||||
border-color: #26962F;
|
||||
|
||||
& + label {
|
||||
color: #26962F !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* &.error, &.valid {
|
||||
transition: .2s border-width;
|
||||
} */
|
||||
|
||||
&:focus ~ .arrow-down {
|
||||
margin-top: -4px;
|
||||
transform: rotate(225deg);
|
||||
-webkit-transform: rotate(225deg);
|
||||
border-color: $button-primary-background;
|
||||
}
|
||||
|
||||
&:focus + label {
|
||||
color: $button-primary-background;
|
||||
}
|
||||
|
||||
&:focus + label, &:valid + label, &:disabled + label {
|
||||
top: -.5rem;
|
||||
transform: none;
|
||||
padding: 0 5px;
|
||||
left: .75rem;
|
||||
font-size: 0.75rem!important;
|
||||
//color: #666;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.input-wrapper > * + * {
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.select-wrapper {
|
||||
max-height: 23.5rem;
|
||||
/* height: auto; */
|
||||
@ -640,23 +507,6 @@ hr {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
::placeholder { /* Chrome, Firefox, Opera, Safari 10.1+ */
|
||||
color: #909192;
|
||||
opacity: 1; /* Firefox */
|
||||
}
|
||||
|
||||
:-ms-input-placeholder { /* Internet Explorer 10-11 */
|
||||
color: #a2acb4;
|
||||
}
|
||||
|
||||
::-ms-input-placeholder { /* Microsoft Edge */
|
||||
color: #a2acb4;
|
||||
}
|
||||
|
||||
input:focus, button:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
// this dimensions will be used for monkey business
|
||||
.auth-image {
|
||||
width: 166px;
|
||||
@ -787,8 +637,19 @@ img.emoji {
|
||||
}
|
||||
}
|
||||
|
||||
[contenteditable=true] {
|
||||
[contenteditable] {
|
||||
user-select: text;
|
||||
outline: none;
|
||||
cursor: text;
|
||||
|
||||
&[data-placeholder] {
|
||||
&:empty:before {
|
||||
content: attr(data-placeholder);
|
||||
color: #a2acb4;
|
||||
display: block; /* For Firefox By Ariel Flesler */
|
||||
cursor: text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sticky_sentinel {
|
||||
|
Loading…
x
Reference in New Issue
Block a user