diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts
index 1287fb2c..9b19af97 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, CLICK_EVENT_NAME, findUpClassName, getRichValue, isInputEmpty, placeCaretAtEnd, serializeNodes } from "../../helpers/dom";
+import { blurActiveElement, cancelEvent, CLICK_EVENT_NAME, findUpClassName, getRichValue, getSelectedNodes, isInputEmpty, isSelectionSingle, markdownTags, MarkdownType, placeCaretAtEnd, serializeNodes } from "../../helpers/dom";
import ButtonMenu, { ButtonMenuItemOptions } from '../buttonMenu';
import emoticonsDropdown from "../emoticonsDropdown";
import PopupCreatePoll from "../popupCreatePoll";
@@ -23,12 +23,252 @@ import { toast } from "../toast";
import { wrapReply } from "../wrappers";
import InputField from '../inputField';
import { MessageEntity } from '../../layer';
+import ButtonIcon from '../buttonIcon';
const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply';
+export class MarkupTooltip {
+ public container: HTMLElement;
+ private wrapper: HTMLElement;
+ private buttons: {[type in MarkdownType]: HTMLElement} = {} as any;
+ private linkBackButton: HTMLElement;
+ private hideTimeout: number;
+ private inputs: HTMLElement[] = [];
+ private addedListener = false;
+ private waitingForMouseUp = false;
+ private linkInput: HTMLInputElement;
+ private savedRange: Range;
+
+ private init() {
+ this.container = document.createElement('div');
+ this.container.classList.add('markup-tooltip', 'z-depth-1', 'hide');
+
+ this.wrapper = document.createElement('div');
+ this.wrapper.classList.add('markup-tooltip-wrapper');
+
+ const tools1 = document.createElement('div');
+ const tools2 = document.createElement('div');
+ tools1.classList.add('markup-tooltip-tools');
+ tools2.classList.add('markup-tooltip-tools');
+
+ const arr = ['bold', 'italic', 'underline', 'strikethrough', 'monospace', 'link'] as (keyof MarkupTooltip['buttons'])[];
+ arr.forEach(c => {
+ const button = ButtonIcon(c, {noRipple: true});
+ tools1.append(this.buttons[c] = button);
+
+ if(c !== 'link') {
+ button.addEventListener('click', () => {
+ appImManager.chatInputC.applyMarkdown(c);
+ });
+ } else {
+ button.addEventListener('click', () => {
+ this.container.classList.add('is-link');
+
+ if(button.classList.contains('active')) {
+ const startContainer = this.savedRange.startContainer;
+ const anchor = startContainer.parentElement as HTMLAnchorElement;
+ this.linkInput.value = anchor.href;
+ } else {
+ this.linkInput.value = '';
+ }
+ });
+ }
+ });
+
+ this.linkBackButton = ButtonIcon('back', {noRipple: true});
+ this.linkInput = document.createElement('input');
+ this.linkInput.placeholder = 'Enter URL...';
+ this.linkInput.classList.add('input-clear');
+ this.linkInput.addEventListener('keydown', (e) => {
+ if(e.code == 'Enter') {
+ const valid = !this.linkInput.value.length || RichTextProcessor.matchUrl(this.linkInput.value);///^(http)|(https):\/\//i.test(this.linkInput.value);
+ if(!valid) {
+ if(this.linkInput.classList.contains('error')) {
+ this.linkInput.classList.remove('error');
+ void this.linkInput.offsetLeft; // reflow
+ }
+
+ this.linkInput.classList.add('error');
+ } else {
+ cancelEvent(e);
+ this.resetSelection();
+ appImManager.chatInputC.applyMarkdown('link', this.linkInput.value);
+ this.hide();
+ }
+ } else {
+ this.linkInput.classList.remove('error');
+ }
+ });
+
+ this.linkBackButton.addEventListener('click', () => {
+ this.container.classList.remove('is-link');
+ //input.value = '';
+ this.resetSelection();
+ });
+
+ const delimiter1 = document.createElement('span');
+ const delimiter2 = document.createElement('span');
+ delimiter1.classList.add('markup-tooltip-delimiter');
+ delimiter2.classList.add('markup-tooltip-delimiter');
+ tools1.insertBefore(delimiter1, this.buttons.link);
+ tools2.append(this.linkBackButton, delimiter2, this.linkInput);
+ //tools1.insertBefore(delimiter2, this.buttons.link.nextSibling);
+
+ this.wrapper.append(tools1, tools2);
+ this.container.append(this.wrapper);
+ document.body.append(this.container);
+ }
+
+ private resetSelection() {
+ const selection = window.getSelection();
+ selection.removeAllRanges();
+ selection.addRange(this.savedRange);
+ this.inputs[0].focus();
+ }
+
+ public hide() {
+ if(this.init) return;
+
+ this.container.classList.remove('is-visible');
+ document.removeEventListener('mouseup', this.onMouseUp);
+ if(this.hideTimeout) clearTimeout(this.hideTimeout);
+ this.hideTimeout = window.setTimeout(() => {
+ this.hideTimeout = undefined;
+ this.container.classList.add('hide');
+ this.container.classList.remove('is-link');
+ }, 200);
+ }
+
+ public getActiveMarkupButton() {
+ const nodes = getSelectedNodes();
+ const parents = [...new Set(nodes.map(node => node.parentNode))];
+ if(parents.length > 1) return undefined;
+
+ const node = parents[0] as HTMLElement;
+ let currentMarkup: HTMLElement;
+ for(const type in markdownTags) {
+ const tag = markdownTags[type as MarkdownType];
+ if(node.matches(tag.match)) {
+ currentMarkup = this.buttons[type as MarkdownType];
+ break;
+ }
+ }
+
+ return currentMarkup;
+ }
+
+ public setActiveMarkupButton() {
+ const activeButton = this.getActiveMarkupButton();
+
+ for(const i in this.buttons) {
+ // @ts-ignore
+ const button = this.buttons[i];
+ if(button != activeButton) {
+ button.classList.remove('active');
+ }
+ }
+
+ if(activeButton) {
+ activeButton.classList.add('active');
+ }
+
+ return activeButton;
+ }
+
+ public show() {
+ if(this.init) {
+ this.init();
+ this.init = null;
+ }
+
+ const selection = document.getSelection();
+
+ if(!selection.toString().trim().length) {
+ this.hide();
+ return;
+ }
+
+ if(this.hideTimeout !== undefined) {
+ clearTimeout(this.hideTimeout);
+ }
+
+ const range = this.savedRange = selection.getRangeAt(0);
+
+ const activeButton = this.setActiveMarkupButton();
+
+ this.container.classList.remove('is-link');
+ const isFirstShow = this.container.classList.contains('hide');
+ if(isFirstShow) {
+ this.container.classList.remove('hide');
+ this.container.classList.add('no-transition');
+ }
+
+ const selectionRect = range.getBoundingClientRect();
+ //const containerRect = this.container.getBoundingClientRect();
+ const sizesRect = this.container.firstElementChild.firstElementChild.getBoundingClientRect();
+ const top = selectionRect.top - sizesRect.height - 8;
+ const left = selectionRect.left + (selectionRect.width - sizesRect.width) / 2;
+ //const top = selectionRect.top - 44 - 8;
+
+ this.container.style.transform = `translate3d(${left}px, ${top}px, 0)`;
+
+ if(isFirstShow) {
+ void this.container.offsetLeft; // reflow
+ this.container.classList.remove('no-transition');
+ }
+
+ this.container.classList.add('is-visible');
+
+ console.log('selection', selectionRect, activeButton);
+ }
+
+ private onMouseUp = (e: Event) => {
+ if(findUpClassName(e.target, 'markup-tooltip')) return;
+ this.hide();
+ document.removeEventListener('mouseup', this.onMouseUp);
+ };
+
+ public setMouseUpEvent() {
+ if(this.waitingForMouseUp) return;
+ this.waitingForMouseUp = true;
+ document.addEventListener('mouseup', (e) => {
+ this.waitingForMouseUp = false;
+ this.show();
+
+ document.addEventListener('mouseup', this.onMouseUp);
+ }, {once: true});
+ }
+
+ public handleSelection(input: HTMLElement) {
+ this.inputs.push(input);
+
+ if(this.addedListener) return;
+ this.addedListener = true;
+ document.addEventListener('selectionchange', (e) => {
+ if(document.activeElement == this.linkInput) {
+ return;
+ }
+
+ if(!this.inputs.includes(document.activeElement as HTMLElement)) {
+ this.hide();
+ return;
+ }
+
+ const selection = document.getSelection();
+
+ if(!selection.toString().trim().length) {
+ this.hide();
+ return;
+ }
+
+ this.setMouseUpEvent();
+ });
+ }
+}
+
export class ChatInput {
public pageEl = document.getElementById('page-chats') as HTMLDivElement;
public messageInput: HTMLDivElement/* HTMLInputElement */;
@@ -82,7 +322,13 @@ export class ChatInput {
readonly executedHistory: string[] = [];
private canUndoFromHTML = '';
+ public markupTooltip: MarkupTooltip;
+
constructor() {
+ if(!isTouchSupported) {
+ this.markupTooltip = new MarkupTooltip();
+ }
+
this.attachMessageInputField();
this.attachMenu = document.getElementById('attach-file') as HTMLButtonElement;
@@ -314,7 +560,24 @@ export class ChatInput {
});
}
+ this.messageInput.addEventListener('beforeinput', (e: Event) => {
+ // * validate due to manual formatting through browser's context menu
+ const inputType = (e as InputEvent).inputType;
+ //console.log('message beforeinput event', e);
+
+ if(inputType.indexOf('format') === 0) {
+ //console.log('message beforeinput format', e, inputType, this.messageInput.innerHTML);
+ const markdownType = inputType.split('format')[1].toLowerCase() as MarkdownType;
+ if(this.applyMarkdown(markdownType)) {
+ cancelEvent(e); // * cancel legacy markdown event
+ }
+ }
+ });
this.messageInput.addEventListener('input', this.onMessageInput);
+
+ if(this.markupTooltip) {
+ this.markupTooltip.handleSelection(this.messageInput);
+ }
}
private onDocumentPaste = (e: ClipboardEvent) => {
@@ -377,107 +640,110 @@ export class ChatInput {
}
};
- private handleMarkdownShortcut = (e: KeyboardEvent) => {
- const formatKeys: {[key: string]: string | (() => void)} = {
- 'B': 'Bold',
- 'I': 'Italic',
- 'U': 'Underline',
- 'S': 'Strikethrough',
- 'M': () => document.execCommand('fontName', false, 'monospace')
+ public applyMarkdown(type: MarkdownType, href?: string) {
+ const commandsMap: Partial<{[key in typeof type]: string | (() => void)}> = {
+ bold: 'Bold',
+ italic: 'Italic',
+ underline: 'Underline',
+ strikethrough: 'Strikethrough',
+ monospace: () => document.execCommand('fontName', false, 'monospace'),
+ link: href ? () => document.execCommand('createLink', false, href) : () => document.execCommand('unlink', false, null)
};
- for(const key in formatKeys) {
- const good = e.code == ('Key' + key);
- if(good) {
- const getSelectedNodes = () => {
- const nodes: Node[] = [];
- const selection = window.getSelection();
- for(let i = 0; i < selection.rangeCount; ++i) {
- const range = selection.getRangeAt(i);
- let {startContainer, endContainer} = range;
- if(endContainer.nodeType != 3) endContainer = endContainer.firstChild;
-
- while(startContainer && startContainer != endContainer) {
- nodes.push(startContainer.nodeType == 3 ? startContainer : startContainer.firstChild);
- startContainer = startContainer.nextSibling;
- }
-
- if(nodes[nodes.length - 1] != endContainer) {
- nodes.push(endContainer);
- }
- }
+ if(!commandsMap[type]) {
+ return false;
+ }
- // * filter null's due to
- return nodes.filter(node => !!node);
- };
-
- const saveExecuted = this.prepareDocumentExecute();
- const executed: any[] = [];
- /**
- * * clear previous formatting, due to Telegram's inability to handle several entities
- */
- const checkForSingle = () => {
- const nodes = getSelectedNodes();
- console.log('Using formatting:', formatKeys[key], nodes, this.executedHistory);
-
- const parents = [...new Set(nodes.map(node => node.parentNode))];
- //const differentParents = !!nodes.find(node => node.parentNode != firstParent);
- const differentParents = parents.length > 1;
-
- let notSingle = false;
- if(differentParents) {
- notSingle = true;
- } else {
- const node = nodes[0];
- if(node && (node.parentNode as HTMLElement) != this.messageInput && (node.parentNode.parentNode as HTMLElement) != this.messageInput) {
- notSingle = true;
- }
- }
+ const command = commandsMap[type];
- if(notSingle) {
- if(key == 'M') {
- executed.push(document.execCommand('styleWithCSS', false, 'true'));
- }
+ //type = 'monospace';
- executed.push(document.execCommand('unlink', false, null));
- executed.push(document.execCommand('removeFormat', false, null));
- // @ts-ignore
- executed.push(typeof(formatKeys[key]) === 'function' ? formatKeys[key]() : document.execCommand(formatKeys[key], false, null));
+ const saveExecuted = this.prepareDocumentExecute();
+ const executed: any[] = [];
+ /**
+ * * clear previous formatting, due to Telegram's inability to handle several entities
+ */
+ const checkForSingle = () => {
+ const nodes = getSelectedNodes();
+ //console.log('Using formatting:', commandsMap[type], nodes, this.executedHistory);
- if(key == 'M') {
- executed.push(document.execCommand('styleWithCSS', false, 'false'));
- }
- }
- };
-
- if(key == 'M') {
- let haveMonospace = false;
- executed.push(document.execCommand('styleWithCSS', false, 'true'));
+ const parents = [...new Set(nodes.map(node => node.parentNode))];
+ //const differentParents = !!nodes.find(node => node.parentNode != firstParent);
+ const differentParents = parents.length > 1;
- const selection = window.getSelection();
- if(!selection.isCollapsed) {
- const range = selection.getRangeAt(0);
- // @ts-ignore
- if(range.commonAncestorContainer.parentNode.tagName == 'SPAN' || range.commonAncestorContainer.tagName == 'SPAN') {
- haveMonospace = true;
- }
- }
+ let notSingle = false;
+ if(differentParents) {
+ notSingle = true;
+ } else {
+ const node = nodes[0];
+ if(node && (node.parentNode as HTMLElement) != this.messageInput && (node.parentNode.parentNode as HTMLElement) != this.messageInput) {
+ notSingle = true;
+ }
+ }
- executed.push(document.execCommand('removeFormat', false, null));
-
- if(!haveMonospace) {
- // @ts-ignore
- executed.push(typeof(formatKeys[key]) === 'function' ? formatKeys[key]() : document.execCommand(formatKeys[key], false, null));
- }
+ if(notSingle) {
+ //if(type === 'monospace') {
+ executed.push(document.execCommand('styleWithCSS', false, 'true'));
+ //}
+
+ executed.push(document.execCommand('unlink', false, null));
+ executed.push(document.execCommand('removeFormat', false, null));
+ executed.push(typeof(command) === 'function' ? command() : document.execCommand(command, false, null));
+ //if(type === 'monospace') {
executed.push(document.execCommand('styleWithCSS', false, 'false'));
- } else {
- // @ts-ignore
- executed.push(typeof(formatKeys[key]) === 'function' ? formatKeys[key]() : document.execCommand(formatKeys[key], false, null));
+ //}
+ }
+ };
+
+ //if(type === 'monospace') {
+ let haveThisType = false;
+ executed.push(document.execCommand('styleWithCSS', false, 'true'));
+
+ const selection = window.getSelection();
+ if(!selection.isCollapsed) {
+ const range = selection.getRangeAt(0);
+ const tag = markdownTags[type];
+
+ const node = range.commonAncestorContainer;
+ if((node.parentNode as HTMLElement).matches(tag.match) || (node instanceof HTMLElement && node.matches(tag.match))) {
+ haveThisType = true;
}
-
- checkForSingle();
- saveExecuted();
+ }
+
+ executed.push(document.execCommand('removeFormat', false, null));
+
+ if(!haveThisType) {
+ executed.push(typeof(command) === 'function' ? command() : document.execCommand(command, false, null));
+ }
+
+ executed.push(document.execCommand('styleWithCSS', false, 'false'));
+ /* } else {
+ executed.push(typeof(command) === 'function' ? command() : document.execCommand(command, false, null));
+ } */
+
+ checkForSingle();
+ saveExecuted();
+ if(this.markupTooltip) {
+ this.markupTooltip.setActiveMarkupButton();
+ }
+
+ return true;
+ }
+
+ private handleMarkdownShortcut = (e: KeyboardEvent) => {
+ const formatKeys: {[key: string]: MarkdownType} = {
+ 'B': 'bold',
+ 'I': 'italic',
+ 'U': 'underline',
+ 'S': 'strikethrough',
+ 'M': 'monospace'
+ };
+
+ for(const key in formatKeys) {
+ const good = e.code == ('Key' + key);
+ if(good) {
+ this.applyMarkdown(formatKeys[key]);
cancelEvent(e); // cancel legacy event
break;
}
@@ -509,7 +775,19 @@ export class ChatInput {
}
};
- private onMessageInput = (/* e: Event */) => {
+ private onMessageInput = (e?: Event) => {
+ // * validate due to manual formatting through browser's context menu
+ /* const inputType = (e as InputEvent).inputType;
+ console.log('message input event', e);
+ if(inputType == 'formatBold') {
+ console.log('message input format', this.messageInput.innerHTML);
+ cancelEvent(e);
+ }
+
+ if(!isSelectionSingle()) {
+ alert('not single');
+ } */
+
//console.log('messageInput input', this.messageInput.innerText, this.serializeNodes(Array.from(this.messageInput.childNodes)));
const value = this.messageInput.innerText;
@@ -605,6 +883,7 @@ export class ChatInput {
}
this.chatInput.classList.add('is-locked');
+ blurActiveElement();
this.recorder.start().then(() => {
this.recordCanceled = false;
diff --git a/src/components/inputField.ts b/src/components/inputField.ts
index b822170e..8258ddf7 100644
--- a/src/components/inputField.ts
+++ b/src/components/inputField.ts
@@ -18,11 +18,8 @@ let init = () => {
let entities = RichTextProcessor.parseEntities(text);
//console.log('messageInput paste', text, entities);
entities = entities.filter(e => e._ == 'messageEntityEmoji' || e._ == 'messageEntityLinebreak');
- if(RichTextProcessor.emojiSupported) { // * fix safari emoji
- entities = entities.filter(e => e._ != 'messageEntityEmoji');
- }
//text = RichTextProcessor.wrapEmojiText(text);
- text = RichTextProcessor.wrapRichText(text, {entities, noLinks: true});
+ text = RichTextProcessor.wrapRichText(text, {entities, noLinks: true, wrappingDraft: true});
// console.log('messageInput paste after', text);
diff --git a/src/helpers/dom.ts b/src/helpers/dom.ts
index 28b2a91b..d1bcba47 100644
--- a/src/helpers/dom.ts
+++ b/src/helpers/dom.ts
@@ -1,5 +1,6 @@
import { MOUNT_CLASS_TO } from "../lib/mtproto/mtproto_config";
import { isTouchSupported } from "./touchSupport";
+import { isSafari } from "./userAgent";
/* export function isInDOM(element: Element, parentNode?: HTMLElement): boolean {
if(!element) {
@@ -120,34 +121,46 @@ export function getRichValue(field: HTMLElement) {
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.getRichValue = getRichValue);
-const markdownTags = [{
- tagName: 'STRONG',
- markdown: '**'
-}, {
- tagName: 'B', // * legacy (Ctrl+B)
- markdown: '**'
-}, {
- tagName: 'U', // * legacy (Ctrl+I)
- markdown: '_-_'
-}, {
- tagName: 'I', // * legacy (Ctrl+I)
- markdown: '__'
-}, {
- tagName: 'EM',
- markdown: '__'
-}, {
- tagName: 'CODE',
- markdown: '`'
-}, {
- tagName: 'PRE',
- markdown: '``'
-}, {
- tagName: 'DEL',
- markdown: '~~'
-}, {
- tagName: 'A',
- markdown: (node: HTMLElement) => `[${(node.parentElement as HTMLAnchorElement).href}](${node.nodeValue})`
-}];
+const markdownTypes = {
+ bold: '**',
+ underline: '_-_',
+ italic: '__',
+ monospace: '`',
+ pre: '``',
+ strikethrough: '~~'
+};
+
+export type MarkdownType = 'bold' | 'italic' | 'underline' | 'strikethrough' | 'monospace' | 'link';
+export type MarkdownTag = {
+ match: string,
+ markdown: string | ((node: HTMLElement) => string)
+};
+export const markdownTags: {[type in MarkdownType]: MarkdownTag} = {
+ bold: {
+ match: '[style*="font-weight"]',
+ markdown: markdownTypes.bold
+ },
+ underline: {
+ match: isSafari ? '[style="text-decoration: underline;"]' : '[style="text-decoration-line: underline;"]',
+ markdown: markdownTypes.underline
+ },
+ italic: {
+ match: '[style="font-style: italic;"]',
+ markdown: markdownTypes.italic
+ },
+ monospace: {
+ match: '[style="font-family: monospace;"]',
+ markdown: markdownTypes.monospace
+ },
+ strikethrough: {
+ match: isSafari ? '[style="text-decoration: line-through;"]' : '[style="text-decoration-line: line-through;"]',
+ markdown: markdownTypes.strikethrough
+ },
+ link: {
+ match: 'A',
+ markdown: (node: HTMLElement) => `[${(node.parentElement as HTMLAnchorElement).href}](${node.nodeValue})`
+ }
+};
export function getRichElementValue(node: HTMLElement, lines: string[], line: string[], selNode?: Node, selOffset?: number) {
if(node.nodeType == 3) { // TEXT
if(selNode === node) {
@@ -156,8 +169,17 @@ export function getRichElementValue(node: HTMLElement, lines: string[], line: st
} else {
let markdown: string;
if(node.parentNode) {
- const tagName = node.parentElement.tagName;
- const markdownTag = markdownTags.find(m => m.tagName == tagName);
+ const parentElement = node.parentElement;
+
+ let markdownTag: MarkdownTag;
+ for(const type in markdownTags) {
+ const tag = markdownTags[type as MarkdownType];
+ if(parentElement.matches(tag.match)) {
+ markdownTag = tag;
+ break;
+ }
+ }
+
if(markdownTag) {
if(typeof(markdownTag.markdown) === 'function') {
line.push(markdownTag.markdown(node));
@@ -476,3 +498,43 @@ export const detachClickEvent = (elem: HTMLElement, callback: (e: TouchEvent | M
elem.removeEventListener(CLICK_EVENT_NAME, callback, options);
}
};
+
+export const getSelectedNodes = () => {
+ const nodes: Node[] = [];
+ const selection = window.getSelection();
+ for(let i = 0; i < selection.rangeCount; ++i) {
+ const range = selection.getRangeAt(i);
+ let {startContainer, endContainer} = range;
+ if(endContainer.nodeType != 3) endContainer = endContainer.firstChild;
+
+ while(startContainer && startContainer != endContainer) {
+ nodes.push(startContainer.nodeType == 3 ? startContainer : startContainer.firstChild);
+ startContainer = startContainer.nextSibling;
+ }
+
+ if(nodes[nodes.length - 1] != endContainer) {
+ nodes.push(endContainer);
+ }
+ }
+
+ // * filter null's due to
+ return nodes.filter(node => !!node);
+};
+
+export const isSelectionSingle = (input: Element = document.activeElement) => {
+ const nodes = getSelectedNodes();
+ const parents = [...new Set(nodes.map(node => node.parentNode))];
+ const differentParents = parents.length > 1;
+
+ let single = true;
+ if(differentParents) {
+ single = false;
+ } else {
+ const node = nodes[0];
+ if(node && node.parentNode != input && node.parentNode.parentNode != input) {
+ single = false;
+ }
+ }
+
+ return single;
+};
diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts
index a56bd4c5..0cf11afe 100644
--- a/src/lib/appManagers/appImManager.ts
+++ b/src/lib/appManagers/appImManager.ts
@@ -39,7 +39,7 @@ import apiManager from '../mtproto/mtprotoworker';
import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config';
import { RichTextProcessor } from "../richtextprocessor";
import rootScope from '../rootScope';
-import { attachClickEvent, cancelEvent, CLICK_EVENT_NAME, findUpClassName, findUpTag, placeCaretAtEnd, whichChild } from "../../helpers/dom";
+import { attachClickEvent, blurActiveElement, cancelEvent, cancelSelection, CLICK_EVENT_NAME, findUpClassName, findUpTag, placeCaretAtEnd, whichChild } from "../../helpers/dom";
import apiUpdatesManager from './apiUpdatesManager';
import appChatsManager, { Channel, Chat } from "./appChatsManager";
import appDialogsManager from "./appDialogsManager";
@@ -703,7 +703,9 @@ export class AppImManager {
AppMediaViewer.buttons.close.click();
} else */ /* if(appSidebarRight.historyTabIDs.slice(-1)[0] == AppSidebarRight.SLIDERITEMSIDS.forward) {
appSidebarRight.forwardTab.closeBtn.click();
- } else */ if(this.chatSelection.isSelecting) {
+ } else */if(this.chatInputC.markupTooltip && this.chatInputC.markupTooltip.container.classList.contains('is-visible')) {
+ this.chatInputC.markupTooltip.hide();
+ } else if(this.chatSelection.isSelecting) {
this.chatSelection.cancelSelection();
} else if(this.columnEl.classList.contains('is-helper-active')) {
this.chatInputC.replyElements.cancelBtn.click();
@@ -1108,8 +1110,10 @@ export class AppImManager {
//this.lazyLoadQueue.clear();
// clear input
+ cancelSelection();
this.chatInputC.clearInput();
- this.chatInputC.replyElements.cancelBtn.click();
+ this.chatInputC.clearHelper();
+ //this.chatInputC.replyElements.cancelBtn.click();
// clear messages
if(bubblesToo) {
diff --git a/src/lib/richtextprocessor.ts b/src/lib/richtextprocessor.ts
index cf4ee681..05da901b 100644
--- a/src/lib/richtextprocessor.ts
+++ b/src/lib/richtextprocessor.ts
@@ -6,6 +6,7 @@ import { MOUNT_CLASS_TO } from './mtproto/mtproto_config';
import { MessageEntity } from '../layer';
import { copy } from '../helpers/object';
import { encodeEntities } from '../helpers/string';
+import { isSafari } from '../helpers/userAgent';
const EmojiHelper = {
emojiMap: (code: string) => { return code; },
@@ -379,7 +380,7 @@ namespace RichTextProcessor {
noLinks: true,
noLinebreaks: true,
noCommands: true,
- noEmphasis: true,
+ wrappingDraft: true,
fromBot: boolean,
noTextFormat: true,
passEntities: Partial<{
@@ -433,7 +434,8 @@ namespace RichTextProcessor {
' href="',
contextUrl.replace('{1}', encodeURIComponent(username)),
'">',
- encodeEntities(entityText),
+ wrapRichNestedText(entityText, entity.nested, options),
+ //encodeEntities(entityText),
''
)
break;
@@ -448,7 +450,7 @@ namespace RichTextProcessor {
'',
- encodeEntities(entityText),
+ wrapRichNestedText(entityText, entity.nested, options),
''
);
break;
@@ -492,7 +494,7 @@ namespace RichTextProcessor {
case 'messageEntityTextUrl':
let inner: string;
let url: string;
- if (entity._ == 'messageEntityTextUrl') {
+ if(entity._ == 'messageEntityTextUrl') {
url = (entity as MessageEntity.messageEntityTextUrl).url;
url = wrapUrl(url, true);
inner = wrapRichNestedText(entityText, entity.nested, options);
@@ -519,6 +521,11 @@ namespace RichTextProcessor {
break;
case 'messageEntityEmoji':
+ if(options.wrappingDraft && emojiSupported) { // * fix safari emoji
+ html.push(encodeEntities(entityText));
+ break;
+ }
+
html.push(emojiSupported ? // ! contenteditable="false" нужен для поля ввода, иначе там будет меняться шрифт в Safari, или же рендерить смайлик напрямую, без контейнера
`${encodeEntities(entityText)}` :
`
`);
@@ -554,29 +561,27 @@ namespace RichTextProcessor {
html.push(wrapRichNestedText(entityText, entity.nested, options));
break;
}
-
- const tag = options.noEmphasis ? 'b' : 'strong';
- html.push(
- `<${tag}>`,
- wrapRichNestedText(entityText, entity.nested, options),
- `${tag}>`
- );
+
+ if(options.wrappingDraft) {
+ html.push(`${wrapRichNestedText(entityText, entity.nested, options)}`);
+ } else {
+ html.push(`${wrapRichNestedText(entityText, entity.nested, options)}`);
+ }
break;
}
-
case 'messageEntityItalic': {
if(options.noTextFormat) {
html.push(wrapRichNestedText(entityText, entity.nested, options));
break;
}
-
- const tag = options.noEmphasis ? 'i' : 'em';
- html.push(
- `<${tag}>`,
- wrapRichNestedText(entityText, entity.nested, options),
- `${tag}>`
- );
+
+ if(options.wrappingDraft) {
+ html.push(`${wrapRichNestedText(entityText, entity.nested, options)}`);
+ } else {
+ html.push(`${wrapRichNestedText(entityText, entity.nested, options)}`);
+ }
+
break;
}
@@ -589,19 +594,21 @@ namespace RichTextProcessor {
break;
case 'messageEntityStrike':
- html.push(
- '',
- wrapRichNestedText(entityText, entity.nested, options),
- ''
- );
+ if(options.wrappingDraft) {
+ const styleName = isSafari ? 'text-decoration' : 'text-decoration-line';
+ html.push(`${wrapRichNestedText(entityText, entity.nested, options)}`);
+ } else {
+ html.push(`${wrapRichNestedText(entityText, entity.nested, options)}`);
+ }
break;
case 'messageEntityUnderline':
- html.push(
- '',
- wrapRichNestedText(entityText, entity.nested, options),
- ''
- );
+ if(options.wrappingDraft) {
+ const styleName = isSafari ? 'text-decoration' : 'text-decoration-line';
+ html.push(`${wrapRichNestedText(entityText, entity.nested, options)}`);
+ } else {
+ html.push(`${wrapRichNestedText(entityText, entity.nested, options)}`);
+ }
break;
case 'messageEntityCode':
@@ -610,11 +617,16 @@ namespace RichTextProcessor {
break;
}
- html.push(
- '',
- encodeEntities(entityText),
- '
'
- );
+ if(options.wrappingDraft) {
+ html.push(`${encodeEntities(entityText)}`);
+ } else {
+ html.push(
+ '',
+ encodeEntities(entityText),
+ '
'
+ );
+ }
+
break;
case 'messageEntityPre':
@@ -728,14 +740,11 @@ namespace RichTextProcessor {
}
let entities = (options.entities || []).slice();
- if(emojiSupported) { // * fix safari emoji
- entities = entities.filter(e => e._ != 'messageEntityEmoji');
- }
return wrapRichText(text, {
entities,
noLinks: true,
- noEmphasis: true,
+ wrappingDraft: true,
passEntities: {
messageEntityTextUrl: true
}
diff --git a/src/scss/partials/_chat.scss b/src/scss/partials/_chat.scss
index 66a04f32..051f9c14 100644
--- a/src/scss/partials/_chat.scss
+++ b/src/scss/partials/_chat.scss
@@ -1324,4 +1324,99 @@ $chat-helper-size: 39px;
border-bottom: 1px solid white;
}
}
-}
\ No newline at end of file
+}
+
+.markup-tooltip {
+ $widthRegular: 218px;
+ $widthLink: 420px;
+ $padding: 7px;
+
+ background: #fff;
+ border-radius: $border-radius-medium;
+ transform: translateZ(0);
+ opacity: 0;
+ transition: opacity var(--layer-transition), transform var(--layer-transition), width var(--layer-transition);
+ position: fixed;
+ left: 0;
+ top: 0;
+ height: 44px;
+ width: $widthRegular;
+ overflow: hidden;
+
+ &-wrapper {
+ position: absolute;
+ left: 0;
+ top: 0;
+ display: flex;
+ align-items: center;
+ justify-content: start;
+ //width: 420px;
+ width: #{$widthRegular + $widthLink};
+ height: 100%;
+ transform: translateX(0);
+ transition: transform var(--layer-transition);
+ }
+
+ &-tools {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: $padding;
+
+ &:first-child {
+ width: $widthRegular;
+ }
+
+ &:last-child {
+ width: $widthLink;
+
+ .markup-tooltip-delimiter {
+ margin-left: .25rem;
+ margin-right: .75rem;
+ }
+ }
+ }
+
+ &-delimiter {
+ width: 1px;
+ height: 25px;
+ background-color: #DADCE0;
+ }
+
+ .btn-icon {
+ border-radius: $border-radius !important;
+ width: 30px;
+ height: 30px;
+ padding: 0;
+
+ &.active {
+ color: #fff!important;
+ background-color: $color-blue!important;
+ }
+ }
+
+ &:not(.is-visible) {
+ pointer-events: none;
+ }
+
+ &.is-visible {
+ opacity: 1;
+ }
+
+ &.is-link {
+ width: $widthLink;
+ }
+
+ &.is-link &-wrapper {
+ transform: translateX(#{-$widthRegular});
+ }
+
+ .input-clear {
+ flex: 1 1 auto;
+ text-overflow: ellipsis;
+ }
+
+ &.no-transition {
+ transition: none;
+ }
+}
diff --git a/src/scss/partials/_fonts.scss b/src/scss/partials/_fonts.scss
index f4385281..d9ee2ed0 100644
--- a/src/scss/partials/_fonts.scss
+++ b/src/scss/partials/_fonts.scss
@@ -2,13 +2,13 @@
@font-face {
font-family: "#{$tgico-font-family}";
- src: url("#{$tgico-font-path}/#{$tgico-font-family}.eot?owpifk");
- src: url("#{$tgico-font-path}/#{$tgico-font-family}.eot?owpifk#iefix")
+ src: url("#{$tgico-font-path}/#{$tgico-font-family}.eot?5dnghg");
+ src: url("#{$tgico-font-path}/#{$tgico-font-family}.eot?5dnghg#iefix")
format("embedded-opentype"),
- url("#{$tgico-font-path}/#{$tgico-font-family}.ttf?owpifk")
+ url("#{$tgico-font-path}/#{$tgico-font-family}.ttf?5dnghg")
format("truetype"),
- url("#{$tgico-font-path}/#{$tgico-font-family}.woff?owpifk") format("woff"),
- url("#{$tgico-font-path}/#{$tgico-font-family}.svg?owpifk##{$tgico-font-family}")
+ url("#{$tgico-font-path}/#{$tgico-font-family}.woff?5dnghg") format("woff"),
+ url("#{$tgico-font-path}/#{$tgico-font-family}.svg?5dnghg##{$tgico-font-family}")
format("svg");
font-weight: normal;
font-style: normal;
@@ -34,348 +34,418 @@
-moz-osx-font-smoothing: grayscale;
}
-.tgico-select:before {
+
+.tgico-dragmedia:before {
content: "\e900";
}
-.tgico-info2:before {
+.tgico-dragfiles:before {
content: "\e901";
}
-.tgico-clouddownload:before {
+.tgico-link:before {
content: "\e902";
}
-.tgico-readchats:before {
+.tgico-monospace:before {
content: "\e903";
}
-.tgico-noncontacts:before {
+.tgico-strikethrough:before {
content: "\e904";
}
-.tgico-bots:before {
+.tgico-underline:before {
content: "\e905";
}
-.tgico-muted:before {
+.tgico-italic:before {
content: "\e906";
}
-.tgico-favourites:before {
+.tgico-bold:before {
content: "\e907";
}
-.tgico-tip:before {
+.tgico-botcom:before {
content: "\e908";
}
-.tgico-loginlogodesktop:before {
+.tgico-calendarfilter:before {
content: "\e909";
}
-.tgico-loginlogomobile:before {
+.tgico-email:before {
content: "\e90a";
}
-.tgico-calendar:before {
+.tgico-passwordoff:before {
content: "\e90b";
}
-.tgico-keyboard:before {
+.tgico-commentssticker:before {
content: "\e90c";
}
-.tgico-gifs:before {
+.tgico-comments:before {
content: "\e90d";
}
-.tgico-stickers:before {
+.tgico-previous:before {
content: "\e90e";
}
-.tgico-deleteleft:before {
+.tgico-next:before {
content: "\e90f";
}
-.tgico-folder:before {
+.tgico-mention:before {
content: "\e910";
}
-.tgico-revote:before {
+.tgico-down:before {
content: "\e911";
}
-.tgico-livelocation:before {
+.tgico-pinlist:before {
content: "\e912";
}
-.tgico-microphone2:before {
+.tgico-replace:before {
content: "\e913";
}
-.tgico-colorize:before {
+.tgico-schedule:before {
content: "\e914";
}
-.tgico-poll:before {
+.tgico-zoomout:before {
content: "\e915";
}
-.tgico-minus:before {
+.tgico-zoomin:before {
content: "\e916";
}
-.tgico-nosound:before {
+.tgico-select:before {
content: "\e917";
}
-.tgico-microphone:before {
+.tgico-info2:before {
content: "\e918";
}
-.tgico-largeplay:before {
+.tgico-clouddownload:before {
content: "\e919";
}
-.tgico-largepause:before {
+.tgico-readchats:before {
content: "\e91a";
}
-.tgico-newchannel:before {
+.tgico-noncontacts:before {
content: "\e91b";
}
-.tgico-newgroup:before {
+.tgico-bots:before {
content: "\e91c";
}
-.tgico-newprivate:before {
+.tgico-muted:before {
content: "\e91d";
}
-.tgico-chatsplaceholder:before {
+.tgico-favourites:before {
content: "\e91e";
}
-.tgico-newchat_filled:before {
+.tgico-tip:before {
content: "\e91f";
}
-.tgico-addmember_filled:before {
+.tgico-loginlogodesktop:before {
content: "\e920";
}
-.tgico-delete:before {
+.tgico-loginlogomobile:before {
content: "\e921";
}
-.tgico-delete_filled:before {
+.tgico-calendar:before {
content: "\e922";
}
-.tgico-send2:before {
+.tgico-keyboard:before {
content: "\e923";
}
-.tgico-avatar_deletedaccount:before {
+.tgico-gifs:before {
content: "\e924";
}
-.tgico-avatar_archivedchats:before {
+.tgico-stickers:before {
content: "\e925";
}
-.tgico-avatar_savedmessages:before {
+.tgico-deleteleft:before {
content: "\e926";
}
-.tgico-pinnedchat:before {
+.tgico-folder:before {
content: "\e927";
}
-.tgico-channelviews:before {
+.tgico-revote:before {
content: "\e928";
}
-.tgico-sendingerror:before {
+.tgico-livelocation:before {
content: "\e929";
}
-.tgico-sending:before {
+.tgico-microphone2:before {
content: "\e92a";
}
-.tgico-check:before {
+.tgico-colorize:before {
content: "\e92b";
}
-.tgico-checks:before {
+.tgico-poll:before {
content: "\e92c";
}
-.tgico-radioon:before {
+.tgico-minus:before {
content: "\e92d";
}
-.tgico-radiooff:before {
+.tgico-nosound:before {
content: "\e92e";
}
-.tgico-checkboxempty:before {
+.tgico-microphone:before {
content: "\e92f";
}
-.tgico-checkboxblock:before {
+.tgico-largeplay:before {
content: "\e930";
}
-.tgico-checkboxon:before {
+.tgico-largepause:before {
content: "\e931";
}
-.tgico-eye2:before {
+.tgico-newchannel:before {
content: "\e932";
}
-.tgico-eye1:before {
+.tgico-newgroup:before {
content: "\e933";
}
-.tgico-fullscreen:before {
+.tgico-newprivate:before {
content: "\e934";
}
-.tgico-smallscreen:before {
+.tgico-chatsplaceholder:before {
content: "\e935";
}
-.tgico-flag:before {
+.tgico-newchat_filled:before {
content: "\e936";
}
-.tgico-lamp:before {
+.tgico-addmember_filled:before {
content: "\e937";
}
-.tgico-sport:before {
+.tgico-delete:before {
content: "\e938";
}
-.tgico-car:before {
+.tgico-delete_filled:before {
content: "\e939";
}
-.tgico-eats:before {
+.tgico-send2:before {
content: "\e93a";
}
-.tgico-animals:before {
+.tgico-avatar_deletedaccount:before {
content: "\e93b";
}
-.tgico-smile:before {
+.tgico-avatar_archivedchats:before {
content: "\e93c";
}
-.tgico-unpin:before {
+.tgico-avatar_savedmessages:before {
content: "\e93d";
}
-.tgico-send:before {
+.tgico-pinnedchat:before {
content: "\e93e";
}
-.tgico-unread:before {
+.tgico-channelviews:before {
content: "\e93f";
}
-.tgico-settings:before {
+.tgico-sendingerror:before {
content: "\e940";
}
-.tgico-edit:before {
+.tgico-sending:before {
content: "\e941";
}
-.tgico-download:before {
+.tgico-check:before {
content: "\e942";
}
-.tgico-cameraadd:before {
+.tgico-checks:before {
content: "\e943";
}
-.tgico-camera:before {
+.tgico-radioon:before {
content: "\e944";
}
-.tgico-permissions:before {
+.tgico-radiooff:before {
content: "\e945";
}
-.tgico-admin:before {
+.tgico-checkboxempty:before {
content: "\e946";
}
-.tgico-stop:before {
+.tgico-checkboxblock:before {
content: "\e947";
}
-.tgico-username:before {
+.tgico-checkboxon:before {
content: "\e948";
}
-.tgico-location:before {
+.tgico-eye2:before {
content: "\e949";
}
-.tgico-info:before {
+.tgico-eye1:before {
content: "\e94a";
}
-.tgico-deleteuser:before {
+.tgico-FullScreen:before {
content: "\e94b";
}
-.tgico-adduser:before {
+.tgico-smallscreen:before {
content: "\e94c";
}
-.tgico-recent:before {
+.tgico-flag:before {
content: "\e94d";
}
-.tgico-channel:before {
+.tgico-lamp:before {
content: "\e94e";
}
-.tgico-document:before {
+.tgico-sport:before {
content: "\e94f";
}
-.tgico-activesessions:before {
+.tgico-car:before {
content: "\e950";
}
-.tgico-logout:before {
+.tgico-eats:before {
content: "\e951";
}
-.tgico-help:before {
+.tgico-animals:before {
content: "\e952";
}
-.tgico-play:before {
+.tgico-smile:before {
content: "\e953";
}
-.tgico-pause:before {
+.tgico-unpin:before {
content: "\e954";
}
-.tgico-reply:before {
+.tgico-send:before {
content: "\e955";
}
-.tgico-forward:before {
+.tgico-unread:before {
content: "\e956";
}
-.tgico-next:before {
+.tgico-settings:before {
content: "\e957";
}
-.tgico-unlock:before {
+.tgico-edit:before {
content: "\e958";
}
-.tgico-lock:before {
+.tgico-download:before {
content: "\e959";
}
-.tgico-data:before {
+.tgico-cameraadd:before {
content: "\e95a";
}
-.tgico-user:before {
+.tgico-camera:before {
content: "\e95b";
}
-.tgico-group:before {
+.tgico-permissions:before {
content: "\e95c";
}
-.tgico-mute:before {
+.tgico-admin:before {
content: "\e95d";
}
-.tgico-unmute:before {
+.tgico-stop:before {
content: "\e95e";
}
-.tgico-photo:before {
+.tgico-username:before {
content: "\e95f";
}
-.tgico-language:before {
+.tgico-location:before {
content: "\e960";
}
-.tgico-message:before {
+.tgico-info:before {
content: "\e961";
}
-.tgico-pin:before {
+.tgico-deleteuser:before {
content: "\e962";
}
-.tgico-attach:before {
+.tgico-adduser:before {
content: "\e963";
}
-.tgico-phone:before {
+.tgico-recent:before {
content: "\e964";
}
-.tgico-savedmessages:before {
+.tgico-channel:before {
content: "\e965";
}
-.tgico-checkbox:before {
+.tgico-document:before {
content: "\e966";
}
-.tgico-copy:before {
+.tgico-activesessions:before {
content: "\e967";
}
-.tgico-unarchive:before {
+.tgico-logout:before {
content: "\e968";
}
-.tgico-archive:before {
+.tgico-help:before {
content: "\e969";
}
-.tgico-check1:before {
+.tgico-play:before {
content: "\e96a";
}
-.tgico-up:before {
+.tgico-pause:before {
content: "\e96b";
}
-.tgico-down:before {
+.tgico-reply:before {
content: "\e96c";
}
-.tgico-close:before {
+.tgico-forward:before {
content: "\e96d";
}
-.tgico-add:before {
+.tgico-next:before {
content: "\e96e";
}
-.tgico-back:before {
+.tgico-unlock:before {
content: "\e96f";
}
-.tgico-more:before {
+.tgico-lock:before {
content: "\e970";
}
-.tgico-menu:before {
+.tgico-data:before {
content: "\e971";
}
-.tgico-search:before {
+.tgico-user:before {
content: "\e972";
}
+.tgico-group:before {
+ content: "\e973";
+}
+.tgico-mute:before {
+ content: "\e974";
+}
+.tgico-unmute:before {
+ content: "\e975";
+}
+.tgico-photo:before {
+ content: "\e976";
+}
+.tgico-language:before {
+ content: "\e977";
+}
+.tgico-message:before {
+ content: "\e978";
+}
+.tgico-pin:before {
+ content: "\e979";
+}
+.tgico-attach:before {
+ content: "\e97a";
+}
+.tgico-phone:before {
+ content: "\e97b";
+}
+.tgico-savedmessages:before {
+ content: "\e97c";
+}
+.tgico-checkbox:before {
+ content: "\e97d";
+}
+.tgico-copy:before {
+ content: "\e97e";
+}
+.tgico-unarchive:before {
+ content: "\e97f";
+}
+.tgico-archive:before {
+ content: "\e980";
+}
+.tgico-check1:before {
+ content: "\e981";
+}
+.tgico-up:before {
+ content: "\e982";
+}
+.tgico-down:before {
+ content: "\e983";
+}
+.tgico-close:before {
+ content: "\e984";
+}
+.tgico-add:before {
+ content: "\e985";
+}
+.tgico-back:before {
+ content: "\e986";
+}
+.tgico-more:before {
+ content: "\e987";
+}
+.tgico-menu:before {
+ content: "\e988";
+}
+.tgico-search:before {
+ content: "\e989";
+}
diff --git a/src/scss/partials/_ico.scss b/src/scss/partials/_ico.scss
index 5ce2becb..7613e604 100644
--- a/src/scss/partials/_ico.scss
+++ b/src/scss/partials/_ico.scss
@@ -13,118 +13,141 @@ $tgico-font-path: "assets/fonts" !default;
.replace(/\.(.+?):before\{content:"(.+?);\}/g, `$$$1: "\\$2;\n`);
*/
-$tgico-select: "\e900";
-$tgico-info2: "\e901";
-$tgico-clouddownload: "\e902";
-$tgico-readchats: "\e903";
-$tgico-noncontacts: "\e904";
-$tgico-bots: "\e905";
-$tgico-muted: "\e906";
-$tgico-favourites: "\e907";
-$tgico-tip: "\e908";
-$tgico-loginlogodesktop: "\e909";
-$tgico-loginlogomobile: "\e90a";
-$tgico-calendar: "\e90b";
-$tgico-keyboard: "\e90c";
-$tgico-gifs: "\e90d";
-$tgico-stickers: "\e90e";
-$tgico-deleteleft: "\e90f";
-$tgico-folder: "\e910";
-$tgico-revote: "\e911";
-$tgico-livelocation: "\e912";
-$tgico-microphone2: "\e913";
-$tgico-colorize: "\e914";
-$tgico-poll: "\e915";
-$tgico-minus: "\e916";
-$tgico-nosound: "\e917";
-$tgico-microphone: "\e918";
-$tgico-largeplay: "\e919";
-$tgico-largepause: "\e91a";
-$tgico-newchannel: "\e91b";
-$tgico-newgroup: "\e91c";
-$tgico-newprivate: "\e91d";
-$tgico-chatsplaceholder: "\e91e";
-$tgico-newchat_filled: "\e91f";
-$tgico-addmember_filled: "\e920";
-$tgico-delete: "\e921";
-$tgico-delete_filled: "\e922";
-$tgico-send2: "\e923";
-$tgico-avatar_deletedaccount: "\e924";
-$tgico-avatar_archivedchats: "\e925";
-$tgico-avatar_savedmessages: "\e926";
-$tgico-pinnedchat: "\e927";
-$tgico-channelviews: "\e928";
-$tgico-sendingerror: "\e929";
-$tgico-sending: "\e92a";
-$tgico-check: "\e92b";
-$tgico-checks: "\e92c";
-$tgico-radioon: "\e92d";
-$tgico-radiooff: "\e92e";
-$tgico-checkboxempty: "\e92f";
-$tgico-checkboxblock: "\e930";
-$tgico-checkboxon: "\e931";
-$tgico-eye2: "\e932";
-$tgico-eye1: "\e933";
-$tgico-fullscreen: "\e934";
-$tgico-smallscreen: "\e935";
-$tgico-flag: "\e936";
-$tgico-lamp: "\e937";
-$tgico-sport: "\e938";
-$tgico-car: "\e939";
-$tgico-eats: "\e93a";
-$tgico-animals: "\e93b";
-$tgico-smile: "\e93c";
-$tgico-unpin: "\e93d";
-$tgico-send: "\e93e";
-$tgico-unread: "\e93f";
-$tgico-settings: "\e940";
-$tgico-edit: "\e941";
-$tgico-download: "\e942";
-$tgico-cameraadd: "\e943";
-$tgico-camera: "\e944";
-$tgico-permissions: "\e945";
-$tgico-admin: "\e946";
-$tgico-stop: "\e947";
-$tgico-username: "\e948";
-$tgico-location: "\e949";
-$tgico-info: "\e94a";
-$tgico-deleteuser: "\e94b";
-$tgico-adduser: "\e94c";
-$tgico-recent: "\e94d";
-$tgico-channel: "\e94e";
-$tgico-document: "\e94f";
-$tgico-activesessions: "\e950";
-$tgico-logout: "\e951";
-$tgico-help: "\e952";
-$tgico-play: "\e953";
-$tgico-pause: "\e954";
-$tgico-reply: "\e955";
-$tgico-forward: "\e956";
-$tgico-next: "\e957";
-$tgico-unlock: "\e958";
-$tgico-lock: "\e959";
-$tgico-data: "\e95a";
-$tgico-user: "\e95b";
-$tgico-group: "\e95c";
-$tgico-mute: "\e95d";
-$tgico-unmute: "\e95e";
-$tgico-photo: "\e95f";
-$tgico-language: "\e960";
-$tgico-message: "\e961";
-$tgico-pin: "\e962";
-$tgico-attach: "\e963";
-$tgico-phone: "\e964";
-$tgico-savedmessages: "\e965";
-$tgico-checkbox: "\e966";
-$tgico-copy: "\e967";
-$tgico-unarchive: "\e968";
-$tgico-archive: "\e969";
-$tgico-check1: "\e96a";
-$tgico-up: "\e96b";
-$tgico-down: "\e96c";
-$tgico-close: "\e96d";
-$tgico-add: "\e96e";
-$tgico-back: "\e96f";
-$tgico-more: "\e970";
-$tgico-menu: "\e971";
-$tgico-search: "\e972";
+$tgico-dragmedia: "\e900";
+$tgico-dragfiles: "\e901";
+$tgico-link: "\e902";
+$tgico-monospace: "\e903";
+$tgico-strikethrough: "\e904";
+$tgico-underline: "\e905";
+$tgico-italic: "\e906";
+$tgico-bold: "\e907";
+$tgico-botcom: "\e908";
+$tgico-calendarfilter: "\e909";
+$tgico-email: "\e90a";
+$tgico-passwordoff: "\e90b";
+$tgico-commentssticker: "\e90c";
+$tgico-comments: "\e90d";
+$tgico-previous: "\e90e";
+$tgico-next: "\e90f";
+$tgico-mention: "\e910";
+$tgico-down: "\e911";
+$tgico-pinlist: "\e912";
+$tgico-replace: "\e913";
+$tgico-schedule: "\e914";
+$tgico-zoomout: "\e915";
+$tgico-zoomin: "\e916";
+$tgico-select: "\e917";
+$tgico-info2: "\e918";
+$tgico-clouddownload: "\e919";
+$tgico-readchats: "\e91a";
+$tgico-noncontacts: "\e91b";
+$tgico-bots: "\e91c";
+$tgico-muted: "\e91d";
+$tgico-favourites: "\e91e";
+$tgico-tip: "\e91f";
+$tgico-loginlogodesktop: "\e920";
+$tgico-loginlogomobile: "\e921";
+$tgico-calendar: "\e922";
+$tgico-keyboard: "\e923";
+$tgico-gifs: "\e924";
+$tgico-stickers: "\e925";
+$tgico-deleteleft: "\e926";
+$tgico-folder: "\e927";
+$tgico-revote: "\e928";
+$tgico-livelocation: "\e929";
+$tgico-microphone2: "\e92a";
+$tgico-colorize: "\e92b";
+$tgico-poll: "\e92c";
+$tgico-minus: "\e92d";
+$tgico-nosound: "\e92e";
+$tgico-microphone: "\e92f";
+$tgico-largeplay: "\e930";
+$tgico-largepause: "\e931";
+$tgico-newchannel: "\e932";
+$tgico-newgroup: "\e933";
+$tgico-newprivate: "\e934";
+$tgico-chatsplaceholder: "\e935";
+$tgico-newchat_filled: "\e936";
+$tgico-addmember_filled: "\e937";
+$tgico-delete: "\e938";
+$tgico-delete_filled: "\e939";
+$tgico-send2: "\e93a";
+$tgico-avatar_deletedaccount: "\e93b";
+$tgico-avatar_archivedchats: "\e93c";
+$tgico-avatar_savedmessages: "\e93d";
+$tgico-pinnedchat: "\e93e";
+$tgico-channelviews: "\e93f";
+$tgico-sendingerror: "\e940";
+$tgico-sending: "\e941";
+$tgico-check: "\e942";
+$tgico-checks: "\e943";
+$tgico-radioon: "\e944";
+$tgico-radiooff: "\e945";
+$tgico-checkboxempty: "\e946";
+$tgico-checkboxblock: "\e947";
+$tgico-checkboxon: "\e948";
+$tgico-eye2: "\e949";
+$tgico-eye1: "\e94a";
+$tgico-fullscreen: "\e94b";
+$tgico-smallscreen: "\e94c";
+$tgico-flag: "\e94d";
+$tgico-lamp: "\e94e";
+$tgico-sport: "\e94f";
+$tgico-car: "\e950";
+$tgico-eats: "\e951";
+$tgico-animals: "\e952";
+$tgico-smile: "\e953";
+$tgico-unpin: "\e954";
+$tgico-send: "\e955";
+$tgico-unread: "\e956";
+$tgico-settings: "\e957";
+$tgico-edit: "\e958";
+$tgico-download: "\e959";
+$tgico-cameraadd: "\e95a";
+$tgico-camera: "\e95b";
+$tgico-permissions: "\e95c";
+$tgico-admin: "\e95d";
+$tgico-stop: "\e95e";
+$tgico-username: "\e95f";
+$tgico-location: "\e960";
+$tgico-info: "\e961";
+$tgico-deleteuser: "\e962";
+$tgico-adduser: "\e963";
+$tgico-recent: "\e964";
+$tgico-channel: "\e965";
+$tgico-document: "\e966";
+$tgico-activesessions: "\e967";
+$tgico-logout: "\e968";
+$tgico-help: "\e969";
+$tgico-play: "\e96a";
+$tgico-pause: "\e96b";
+$tgico-reply: "\e96c";
+$tgico-forward: "\e96d";
+$tgico-next: "\e96e";
+$tgico-unlock: "\e96f";
+$tgico-lock: "\e970";
+$tgico-data: "\e971";
+$tgico-user: "\e972";
+$tgico-group: "\e973";
+$tgico-mute: "\e974";
+$tgico-unmute: "\e975";
+$tgico-photo: "\e976";
+$tgico-language: "\e977";
+$tgico-message: "\e978";
+$tgico-pin: "\e979";
+$tgico-attach: "\e97a";
+$tgico-phone: "\e97b";
+$tgico-savedmessages: "\e97c";
+$tgico-checkbox: "\e97d";
+$tgico-copy: "\e97e";
+$tgico-unarchive: "\e97f";
+$tgico-archive: "\e980";
+$tgico-check1: "\e981";
+$tgico-up: "\e982";
+$tgico-down: "\e983";
+$tgico-close: "\e984";
+$tgico-add: "\e985";
+$tgico-back: "\e986";
+$tgico-more: "\e987";
+$tgico-menu: "\e988";
+$tgico-search: "\e989";
diff --git a/src/scss/partials/_input.scss b/src/scss/partials/_input.scss
index 7afe5d9c..04e1c06b 100644
--- a/src/scss/partials/_input.scss
+++ b/src/scss/partials/_input.scss
@@ -152,4 +152,32 @@
input:focus, button:focus {
outline: none;
+}
+
+.input-clear {
+ outline: none;
+ border: none;
+ padding: 0;
+
+ &.error {
+ animation: input-shake .2s ease-in-out forwards;
+ }
+}
+
+@keyframes input-shake {
+ 0% {
+ transform: translateX(0);
+ }
+
+ 25% {
+ transform: translateX(-.5rem);
+ }
+
+ 75% {
+ transform: translateX(.5rem);
+ }
+
+ 100% {
+ transform: translateX(0);
+ }
}
\ No newline at end of file