Browse Source

Markdown tooltip & fixes

Close keyboard on record
Fix last helper when following by chat
master
Eduard Kuzmenko 4 years ago
parent
commit
cdf6c09ed5
  1. 377
      src/components/chat/input.ts
  2. 5
      src/components/inputField.ts
  3. 120
      src/helpers/dom.ts
  4. 10
      src/lib/appManagers/appImManager.ts
  5. 69
      src/lib/richtextprocessor.ts
  6. 95
      src/scss/partials/_chat.scss
  7. 310
      src/scss/partials/_fonts.scss
  8. 253
      src/scss/partials/_ico.scss
  9. 28
      src/scss/partials/_input.scss

377
src/components/chat/input.ts

@ -11,7 +11,7 @@ import apiManager from "../../lib/mtproto/mtprotoworker";
import opusDecodeController from "../../lib/opusDecodeController"; import opusDecodeController from "../../lib/opusDecodeController";
import { RichTextProcessor } from "../../lib/richtextprocessor"; import { RichTextProcessor } from "../../lib/richtextprocessor";
import rootScope from '../../lib/rootScope'; 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 ButtonMenu, { ButtonMenuItemOptions } from '../buttonMenu';
import emoticonsDropdown from "../emoticonsDropdown"; import emoticonsDropdown from "../emoticonsDropdown";
import PopupCreatePoll from "../popupCreatePoll"; import PopupCreatePoll from "../popupCreatePoll";
@ -23,12 +23,252 @@ import { toast } from "../toast";
import { wrapReply } from "../wrappers"; import { wrapReply } from "../wrappers";
import InputField from '../inputField'; import InputField from '../inputField';
import { MessageEntity } from '../../layer'; import { MessageEntity } from '../../layer';
import ButtonIcon from '../buttonIcon';
const RECORD_MIN_TIME = 500; const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.'; const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply'; 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 { export class ChatInput {
public pageEl = document.getElementById('page-chats') as HTMLDivElement; public pageEl = document.getElementById('page-chats') as HTMLDivElement;
public messageInput: HTMLDivElement/* HTMLInputElement */; public messageInput: HTMLDivElement/* HTMLInputElement */;
@ -82,7 +322,13 @@ export class ChatInput {
readonly executedHistory: string[] = []; readonly executedHistory: string[] = [];
private canUndoFromHTML = ''; private canUndoFromHTML = '';
public markupTooltip: MarkupTooltip;
constructor() { constructor() {
if(!isTouchSupported) {
this.markupTooltip = new MarkupTooltip();
}
this.attachMessageInputField(); this.attachMessageInputField();
this.attachMenu = document.getElementById('attach-file') as HTMLButtonElement; 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); this.messageInput.addEventListener('input', this.onMessageInput);
if(this.markupTooltip) {
this.markupTooltip.handleSelection(this.messageInput);
}
} }
private onDocumentPaste = (e: ClipboardEvent) => { private onDocumentPaste = (e: ClipboardEvent) => {
@ -377,39 +640,23 @@ export class ChatInput {
} }
}; };
private handleMarkdownShortcut = (e: KeyboardEvent) => { public applyMarkdown(type: MarkdownType, href?: string) {
const formatKeys: {[key: string]: string | (() => void)} = { const commandsMap: Partial<{[key in typeof type]: string | (() => void)}> = {
'B': 'Bold', bold: 'Bold',
'I': 'Italic', italic: 'Italic',
'U': 'Underline', underline: 'Underline',
'S': 'Strikethrough', strikethrough: 'Strikethrough',
'M': () => document.execCommand('fontName', false, 'monospace') monospace: () => document.execCommand('fontName', false, 'monospace'),
link: href ? () => document.execCommand('createLink', false, href) : () => document.execCommand('unlink', false, null)
}; };
for(const key in formatKeys) { if(!commandsMap[type]) {
const good = e.code == ('Key' + key); return false;
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) { const command = commandsMap[type];
nodes.push(endContainer);
}
}
// * filter null's due to <br> //type = 'monospace';
return nodes.filter(node => !!node);
};
const saveExecuted = this.prepareDocumentExecute(); const saveExecuted = this.prepareDocumentExecute();
const executed: any[] = []; const executed: any[] = [];
@ -418,7 +665,7 @@ export class ChatInput {
*/ */
const checkForSingle = () => { const checkForSingle = () => {
const nodes = getSelectedNodes(); const nodes = getSelectedNodes();
console.log('Using formatting:', formatKeys[key], nodes, this.executedHistory); //console.log('Using formatting:', commandsMap[type], nodes, this.executedHistory);
const parents = [...new Set(nodes.map(node => node.parentNode))]; const parents = [...new Set(nodes.map(node => node.parentNode))];
//const differentParents = !!nodes.find(node => node.parentNode != firstParent); //const differentParents = !!nodes.find(node => node.parentNode != firstParent);
@ -435,49 +682,68 @@ export class ChatInput {
} }
if(notSingle) { if(notSingle) {
if(key == 'M') { //if(type === 'monospace') {
executed.push(document.execCommand('styleWithCSS', false, 'true')); executed.push(document.execCommand('styleWithCSS', false, 'true'));
} //}
executed.push(document.execCommand('unlink', false, null)); executed.push(document.execCommand('unlink', false, null));
executed.push(document.execCommand('removeFormat', false, null)); executed.push(document.execCommand('removeFormat', false, null));
// @ts-ignore executed.push(typeof(command) === 'function' ? command() : document.execCommand(command, false, null));
executed.push(typeof(formatKeys[key]) === 'function' ? formatKeys[key]() : document.execCommand(formatKeys[key], false, null));
if(key == 'M') { //if(type === 'monospace') {
executed.push(document.execCommand('styleWithCSS', false, 'false')); executed.push(document.execCommand('styleWithCSS', false, 'false'));
} //}
} }
}; };
if(key == 'M') { //if(type === 'monospace') {
let haveMonospace = false; let haveThisType = false;
executed.push(document.execCommand('styleWithCSS', false, 'true')); executed.push(document.execCommand('styleWithCSS', false, 'true'));
const selection = window.getSelection(); const selection = window.getSelection();
if(!selection.isCollapsed) { if(!selection.isCollapsed) {
const range = selection.getRangeAt(0); const range = selection.getRangeAt(0);
// @ts-ignore const tag = markdownTags[type];
if(range.commonAncestorContainer.parentNode.tagName == 'SPAN' || range.commonAncestorContainer.tagName == 'SPAN') {
haveMonospace = true; const node = range.commonAncestorContainer;
if((node.parentNode as HTMLElement).matches(tag.match) || (node instanceof HTMLElement && node.matches(tag.match))) {
haveThisType = true;
} }
} }
executed.push(document.execCommand('removeFormat', false, null)); executed.push(document.execCommand('removeFormat', false, null));
if(!haveMonospace) { if(!haveThisType) {
// @ts-ignore executed.push(typeof(command) === 'function' ? command() : document.execCommand(command, false, null));
executed.push(typeof(formatKeys[key]) === 'function' ? formatKeys[key]() : document.execCommand(formatKeys[key], false, null));
} }
executed.push(document.execCommand('styleWithCSS', false, 'false')); executed.push(document.execCommand('styleWithCSS', false, 'false'));
} else { /* } else {
// @ts-ignore executed.push(typeof(command) === 'function' ? command() : document.execCommand(command, false, null));
executed.push(typeof(formatKeys[key]) === 'function' ? formatKeys[key]() : document.execCommand(formatKeys[key], false, null)); } */
}
checkForSingle(); checkForSingle();
saveExecuted(); 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 cancelEvent(e); // cancel legacy event
break; 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))); //console.log('messageInput input', this.messageInput.innerText, this.serializeNodes(Array.from(this.messageInput.childNodes)));
const value = this.messageInput.innerText; const value = this.messageInput.innerText;
@ -605,6 +883,7 @@ export class ChatInput {
} }
this.chatInput.classList.add('is-locked'); this.chatInput.classList.add('is-locked');
blurActiveElement();
this.recorder.start().then(() => { this.recorder.start().then(() => {
this.recordCanceled = false; this.recordCanceled = false;

5
src/components/inputField.ts

@ -18,11 +18,8 @@ let init = () => {
let entities = RichTextProcessor.parseEntities(text); let entities = RichTextProcessor.parseEntities(text);
//console.log('messageInput paste', text, entities); //console.log('messageInput paste', text, entities);
entities = entities.filter(e => e._ == 'messageEntityEmoji' || e._ == 'messageEntityLinebreak'); 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.wrapEmojiText(text);
text = RichTextProcessor.wrapRichText(text, {entities, noLinks: true}); text = RichTextProcessor.wrapRichText(text, {entities, noLinks: true, wrappingDraft: true});
// console.log('messageInput paste after', text); // console.log('messageInput paste after', text);

120
src/helpers/dom.ts

@ -1,5 +1,6 @@
import { MOUNT_CLASS_TO } from "../lib/mtproto/mtproto_config"; import { MOUNT_CLASS_TO } from "../lib/mtproto/mtproto_config";
import { isTouchSupported } from "./touchSupport"; import { isTouchSupported } from "./touchSupport";
import { isSafari } from "./userAgent";
/* export function isInDOM(element: Element, parentNode?: HTMLElement): boolean { /* export function isInDOM(element: Element, parentNode?: HTMLElement): boolean {
if(!element) { if(!element) {
@ -120,34 +121,46 @@ export function getRichValue(field: HTMLElement) {
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.getRichValue = getRichValue); MOUNT_CLASS_TO && (MOUNT_CLASS_TO.getRichValue = getRichValue);
const markdownTags = [{ const markdownTypes = {
tagName: 'STRONG', bold: '**',
markdown: '**' underline: '_-_',
}, { italic: '__',
tagName: 'B', // * legacy (Ctrl+B) monospace: '`',
markdown: '**' pre: '``',
}, { strikethrough: '~~'
tagName: 'U', // * legacy (Ctrl+I) };
markdown: '_-_'
}, { export type MarkdownType = 'bold' | 'italic' | 'underline' | 'strikethrough' | 'monospace' | 'link';
tagName: 'I', // * legacy (Ctrl+I) export type MarkdownTag = {
markdown: '__' match: string,
}, { markdown: string | ((node: HTMLElement) => string)
tagName: 'EM', };
markdown: '__' export const markdownTags: {[type in MarkdownType]: MarkdownTag} = {
}, { bold: {
tagName: 'CODE', match: '[style*="font-weight"]',
markdown: '`' markdown: markdownTypes.bold
}, { },
tagName: 'PRE', underline: {
markdown: '``' match: isSafari ? '[style="text-decoration: underline;"]' : '[style="text-decoration-line: underline;"]',
}, { markdown: markdownTypes.underline
tagName: 'DEL', },
markdown: '~~' italic: {
}, { match: '[style="font-style: italic;"]',
tagName: 'A', 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})` markdown: (node: HTMLElement) => `[${(node.parentElement as HTMLAnchorElement).href}](${node.nodeValue})`
}]; }
};
export function getRichElementValue(node: HTMLElement, lines: string[], line: string[], selNode?: Node, selOffset?: number) { export function getRichElementValue(node: HTMLElement, lines: string[], line: string[], selNode?: Node, selOffset?: number) {
if(node.nodeType == 3) { // TEXT if(node.nodeType == 3) { // TEXT
if(selNode === node) { if(selNode === node) {
@ -156,8 +169,17 @@ export function getRichElementValue(node: HTMLElement, lines: string[], line: st
} else { } else {
let markdown: string; let markdown: string;
if(node.parentNode) { if(node.parentNode) {
const tagName = node.parentElement.tagName; const parentElement = node.parentElement;
const markdownTag = markdownTags.find(m => m.tagName == tagName);
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(markdownTag) {
if(typeof(markdownTag.markdown) === 'function') { if(typeof(markdownTag.markdown) === 'function') {
line.push(markdownTag.markdown(node)); 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); 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 <br>
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;
};

10
src/lib/appManagers/appImManager.ts

@ -39,7 +39,7 @@ import apiManager from '../mtproto/mtprotoworker';
import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config'; import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config';
import { RichTextProcessor } from "../richtextprocessor"; import { RichTextProcessor } from "../richtextprocessor";
import rootScope from '../rootScope'; 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 apiUpdatesManager from './apiUpdatesManager';
import appChatsManager, { Channel, Chat } from "./appChatsManager"; import appChatsManager, { Channel, Chat } from "./appChatsManager";
import appDialogsManager from "./appDialogsManager"; import appDialogsManager from "./appDialogsManager";
@ -703,7 +703,9 @@ export class AppImManager {
AppMediaViewer.buttons.close.click(); AppMediaViewer.buttons.close.click();
} else */ /* if(appSidebarRight.historyTabIDs.slice(-1)[0] == AppSidebarRight.SLIDERITEMSIDS.forward) { } else */ /* if(appSidebarRight.historyTabIDs.slice(-1)[0] == AppSidebarRight.SLIDERITEMSIDS.forward) {
appSidebarRight.forwardTab.closeBtn.click(); 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(); this.chatSelection.cancelSelection();
} else if(this.columnEl.classList.contains('is-helper-active')) { } else if(this.columnEl.classList.contains('is-helper-active')) {
this.chatInputC.replyElements.cancelBtn.click(); this.chatInputC.replyElements.cancelBtn.click();
@ -1108,8 +1110,10 @@ export class AppImManager {
//this.lazyLoadQueue.clear(); //this.lazyLoadQueue.clear();
// clear input // clear input
cancelSelection();
this.chatInputC.clearInput(); this.chatInputC.clearInput();
this.chatInputC.replyElements.cancelBtn.click(); this.chatInputC.clearHelper();
//this.chatInputC.replyElements.cancelBtn.click();
// clear messages // clear messages
if(bubblesToo) { if(bubblesToo) {

69
src/lib/richtextprocessor.ts

@ -6,6 +6,7 @@ import { MOUNT_CLASS_TO } from './mtproto/mtproto_config';
import { MessageEntity } from '../layer'; import { MessageEntity } from '../layer';
import { copy } from '../helpers/object'; import { copy } from '../helpers/object';
import { encodeEntities } from '../helpers/string'; import { encodeEntities } from '../helpers/string';
import { isSafari } from '../helpers/userAgent';
const EmojiHelper = { const EmojiHelper = {
emojiMap: (code: string) => { return code; }, emojiMap: (code: string) => { return code; },
@ -379,7 +380,7 @@ namespace RichTextProcessor {
noLinks: true, noLinks: true,
noLinebreaks: true, noLinebreaks: true,
noCommands: true, noCommands: true,
noEmphasis: true, wrappingDraft: true,
fromBot: boolean, fromBot: boolean,
noTextFormat: true, noTextFormat: true,
passEntities: Partial<{ passEntities: Partial<{
@ -433,7 +434,8 @@ namespace RichTextProcessor {
' href="', ' href="',
contextUrl.replace('{1}', encodeURIComponent(username)), contextUrl.replace('{1}', encodeURIComponent(username)),
'">', '">',
encodeEntities(entityText), wrapRichNestedText(entityText, entity.nested, options),
//encodeEntities(entityText),
'</a>' '</a>'
) )
break; break;
@ -448,7 +450,7 @@ namespace RichTextProcessor {
'<a href="#/im?p=u', '<a href="#/im?p=u',
encodeURIComponent(entity.user_id), encodeURIComponent(entity.user_id),
'">', '">',
encodeEntities(entityText), wrapRichNestedText(entityText, entity.nested, options),
'</a>' '</a>'
); );
break; break;
@ -519,6 +521,11 @@ namespace RichTextProcessor {
break; break;
case 'messageEntityEmoji': case 'messageEntityEmoji':
if(options.wrappingDraft && emojiSupported) { // * fix safari emoji
html.push(encodeEntities(entityText));
break;
}
html.push(emojiSupported ? // ! contenteditable="false" нужен для поля ввода, иначе там будет меняться шрифт в Safari, или же рендерить смайлик напрямую, без контейнера html.push(emojiSupported ? // ! contenteditable="false" нужен для поля ввода, иначе там будет меняться шрифт в Safari, или же рендерить смайлик напрямую, без контейнера
`<span class="emoji">${encodeEntities(entityText)}</span>` : `<span class="emoji">${encodeEntities(entityText)}</span>` :
`<img src="assets/img/emoji/${entity.unicode}.png" alt="${encodeEntities(entityText)}" class="emoji">`); `<img src="assets/img/emoji/${entity.unicode}.png" alt="${encodeEntities(entityText)}" class="emoji">`);
@ -555,28 +562,26 @@ namespace RichTextProcessor {
break; break;
} }
const tag = options.noEmphasis ? 'b' : 'strong'; if(options.wrappingDraft) {
html.push( html.push(`<span style="font-weight: bold;">${wrapRichNestedText(entityText, entity.nested, options)}</span>`);
`<${tag}>`, } else {
wrapRichNestedText(entityText, entity.nested, options), html.push(`<strong>${wrapRichNestedText(entityText, entity.nested, options)}</strong>`);
`</${tag}>` }
);
break; break;
} }
case 'messageEntityItalic': { case 'messageEntityItalic': {
if(options.noTextFormat) { if(options.noTextFormat) {
html.push(wrapRichNestedText(entityText, entity.nested, options)); html.push(wrapRichNestedText(entityText, entity.nested, options));
break; break;
} }
const tag = options.noEmphasis ? 'i' : 'em'; if(options.wrappingDraft) {
html.push( html.push(`<span style="font-style: italic;">${wrapRichNestedText(entityText, entity.nested, options)}</span>`);
`<${tag}>`, } else {
wrapRichNestedText(entityText, entity.nested, options), html.push(`<em>${wrapRichNestedText(entityText, entity.nested, options)}</em>`);
`</${tag}>` }
);
break; break;
} }
@ -589,19 +594,21 @@ namespace RichTextProcessor {
break; break;
case 'messageEntityStrike': case 'messageEntityStrike':
html.push( if(options.wrappingDraft) {
'<del>', const styleName = isSafari ? 'text-decoration' : 'text-decoration-line';
wrapRichNestedText(entityText, entity.nested, options), html.push(`<span style="${styleName}: line-through;">${wrapRichNestedText(entityText, entity.nested, options)}</span>`);
'</del>' } else {
); html.push(`<del>${wrapRichNestedText(entityText, entity.nested, options)}</del>`);
}
break; break;
case 'messageEntityUnderline': case 'messageEntityUnderline':
html.push( if(options.wrappingDraft) {
'<u>', const styleName = isSafari ? 'text-decoration' : 'text-decoration-line';
wrapRichNestedText(entityText, entity.nested, options), html.push(`<span style="${styleName}: underline;">${wrapRichNestedText(entityText, entity.nested, options)}</span>`);
'</u>' } else {
); html.push(`<u>${wrapRichNestedText(entityText, entity.nested, options)}</u>`);
}
break; break;
case 'messageEntityCode': case 'messageEntityCode':
@ -610,11 +617,16 @@ namespace RichTextProcessor {
break; break;
} }
if(options.wrappingDraft) {
html.push(`<span style="font-family: monospace;">${encodeEntities(entityText)}</span>`);
} else {
html.push( html.push(
'<code>', '<code>',
encodeEntities(entityText), encodeEntities(entityText),
'</code>' '</code>'
); );
}
break; break;
case 'messageEntityPre': case 'messageEntityPre':
@ -728,14 +740,11 @@ namespace RichTextProcessor {
} }
let entities = (options.entities || []).slice(); let entities = (options.entities || []).slice();
if(emojiSupported) { // * fix safari emoji
entities = entities.filter(e => e._ != 'messageEntityEmoji');
}
return wrapRichText(text, { return wrapRichText(text, {
entities, entities,
noLinks: true, noLinks: true,
noEmphasis: true, wrappingDraft: true,
passEntities: { passEntities: {
messageEntityTextUrl: true messageEntityTextUrl: true
} }

95
src/scss/partials/_chat.scss

@ -1325,3 +1325,98 @@ $chat-helper-size: 39px;
} }
} }
} }
.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;
}
}

310
src/scss/partials/_fonts.scss

@ -2,13 +2,13 @@
@font-face { @font-face {
font-family: "#{$tgico-font-family}"; font-family: "#{$tgico-font-family}";
src: url("#{$tgico-font-path}/#{$tgico-font-family}.eot?owpifk"); src: url("#{$tgico-font-path}/#{$tgico-font-family}.eot?5dnghg");
src: url("#{$tgico-font-path}/#{$tgico-font-family}.eot?owpifk#iefix") src: url("#{$tgico-font-path}/#{$tgico-font-family}.eot?5dnghg#iefix")
format("embedded-opentype"), format("embedded-opentype"),
url("#{$tgico-font-path}/#{$tgico-font-family}.ttf?owpifk") url("#{$tgico-font-path}/#{$tgico-font-family}.ttf?5dnghg")
format("truetype"), format("truetype"),
url("#{$tgico-font-path}/#{$tgico-font-family}.woff?owpifk") format("woff"), url("#{$tgico-font-path}/#{$tgico-font-family}.woff?5dnghg") format("woff"),
url("#{$tgico-font-path}/#{$tgico-font-family}.svg?owpifk##{$tgico-font-family}") url("#{$tgico-font-path}/#{$tgico-font-family}.svg?5dnghg##{$tgico-font-family}")
format("svg"); format("svg");
font-weight: normal; font-weight: normal;
font-style: normal; font-style: normal;
@ -34,348 +34,418 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.tgico-select:before {
.tgico-dragmedia:before {
content: "\e900"; content: "\e900";
} }
.tgico-info2:before { .tgico-dragfiles:before {
content: "\e901"; content: "\e901";
} }
.tgico-clouddownload:before { .tgico-link:before {
content: "\e902"; content: "\e902";
} }
.tgico-readchats:before { .tgico-monospace:before {
content: "\e903"; content: "\e903";
} }
.tgico-noncontacts:before { .tgico-strikethrough:before {
content: "\e904"; content: "\e904";
} }
.tgico-bots:before { .tgico-underline:before {
content: "\e905"; content: "\e905";
} }
.tgico-muted:before { .tgico-italic:before {
content: "\e906"; content: "\e906";
} }
.tgico-favourites:before { .tgico-bold:before {
content: "\e907"; content: "\e907";
} }
.tgico-tip:before { .tgico-botcom:before {
content: "\e908"; content: "\e908";
} }
.tgico-loginlogodesktop:before { .tgico-calendarfilter:before {
content: "\e909"; content: "\e909";
} }
.tgico-loginlogomobile:before { .tgico-email:before {
content: "\e90a"; content: "\e90a";
} }
.tgico-calendar:before { .tgico-passwordoff:before {
content: "\e90b"; content: "\e90b";
} }
.tgico-keyboard:before { .tgico-commentssticker:before {
content: "\e90c"; content: "\e90c";
} }
.tgico-gifs:before { .tgico-comments:before {
content: "\e90d"; content: "\e90d";
} }
.tgico-stickers:before { .tgico-previous:before {
content: "\e90e"; content: "\e90e";
} }
.tgico-deleteleft:before { .tgico-next:before {
content: "\e90f"; content: "\e90f";
} }
.tgico-folder:before { .tgico-mention:before {
content: "\e910"; content: "\e910";
} }
.tgico-revote:before { .tgico-down:before {
content: "\e911"; content: "\e911";
} }
.tgico-livelocation:before { .tgico-pinlist:before {
content: "\e912"; content: "\e912";
} }
.tgico-microphone2:before { .tgico-replace:before {
content: "\e913"; content: "\e913";
} }
.tgico-colorize:before { .tgico-schedule:before {
content: "\e914"; content: "\e914";
} }
.tgico-poll:before { .tgico-zoomout:before {
content: "\e915"; content: "\e915";
} }
.tgico-minus:before { .tgico-zoomin:before {
content: "\e916"; content: "\e916";
} }
.tgico-nosound:before { .tgico-select:before {
content: "\e917"; content: "\e917";
} }
.tgico-microphone:before { .tgico-info2:before {
content: "\e918"; content: "\e918";
} }
.tgico-largeplay:before { .tgico-clouddownload:before {
content: "\e919"; content: "\e919";
} }
.tgico-largepause:before { .tgico-readchats:before {
content: "\e91a"; content: "\e91a";
} }
.tgico-newchannel:before { .tgico-noncontacts:before {
content: "\e91b"; content: "\e91b";
} }
.tgico-newgroup:before { .tgico-bots:before {
content: "\e91c"; content: "\e91c";
} }
.tgico-newprivate:before { .tgico-muted:before {
content: "\e91d"; content: "\e91d";
} }
.tgico-chatsplaceholder:before { .tgico-favourites:before {
content: "\e91e"; content: "\e91e";
} }
.tgico-newchat_filled:before { .tgico-tip:before {
content: "\e91f"; content: "\e91f";
} }
.tgico-addmember_filled:before { .tgico-loginlogodesktop:before {
content: "\e920"; content: "\e920";
} }
.tgico-delete:before { .tgico-loginlogomobile:before {
content: "\e921"; content: "\e921";
} }
.tgico-delete_filled:before { .tgico-calendar:before {
content: "\e922"; content: "\e922";
} }
.tgico-send2:before { .tgico-keyboard:before {
content: "\e923"; content: "\e923";
} }
.tgico-avatar_deletedaccount:before { .tgico-gifs:before {
content: "\e924"; content: "\e924";
} }
.tgico-avatar_archivedchats:before { .tgico-stickers:before {
content: "\e925"; content: "\e925";
} }
.tgico-avatar_savedmessages:before { .tgico-deleteleft:before {
content: "\e926"; content: "\e926";
} }
.tgico-pinnedchat:before { .tgico-folder:before {
content: "\e927"; content: "\e927";
} }
.tgico-channelviews:before { .tgico-revote:before {
content: "\e928"; content: "\e928";
} }
.tgico-sendingerror:before { .tgico-livelocation:before {
content: "\e929"; content: "\e929";
} }
.tgico-sending:before { .tgico-microphone2:before {
content: "\e92a"; content: "\e92a";
} }
.tgico-check:before { .tgico-colorize:before {
content: "\e92b"; content: "\e92b";
} }
.tgico-checks:before { .tgico-poll:before {
content: "\e92c"; content: "\e92c";
} }
.tgico-radioon:before { .tgico-minus:before {
content: "\e92d"; content: "\e92d";
} }
.tgico-radiooff:before { .tgico-nosound:before {
content: "\e92e"; content: "\e92e";
} }
.tgico-checkboxempty:before { .tgico-microphone:before {
content: "\e92f"; content: "\e92f";
} }
.tgico-checkboxblock:before { .tgico-largeplay:before {
content: "\e930"; content: "\e930";
} }
.tgico-checkboxon:before { .tgico-largepause:before {
content: "\e931"; content: "\e931";
} }
.tgico-eye2:before { .tgico-newchannel:before {
content: "\e932"; content: "\e932";
} }
.tgico-eye1:before { .tgico-newgroup:before {
content: "\e933"; content: "\e933";
} }
.tgico-fullscreen:before { .tgico-newprivate:before {
content: "\e934"; content: "\e934";
} }
.tgico-smallscreen:before { .tgico-chatsplaceholder:before {
content: "\e935"; content: "\e935";
} }
.tgico-flag:before { .tgico-newchat_filled:before {
content: "\e936"; content: "\e936";
} }
.tgico-lamp:before { .tgico-addmember_filled:before {
content: "\e937"; content: "\e937";
} }
.tgico-sport:before { .tgico-delete:before {
content: "\e938"; content: "\e938";
} }
.tgico-car:before { .tgico-delete_filled:before {
content: "\e939"; content: "\e939";
} }
.tgico-eats:before { .tgico-send2:before {
content: "\e93a"; content: "\e93a";
} }
.tgico-animals:before { .tgico-avatar_deletedaccount:before {
content: "\e93b"; content: "\e93b";
} }
.tgico-smile:before { .tgico-avatar_archivedchats:before {
content: "\e93c"; content: "\e93c";
} }
.tgico-unpin:before { .tgico-avatar_savedmessages:before {
content: "\e93d"; content: "\e93d";
} }
.tgico-send:before { .tgico-pinnedchat:before {
content: "\e93e"; content: "\e93e";
} }
.tgico-unread:before { .tgico-channelviews:before {
content: "\e93f"; content: "\e93f";
} }
.tgico-settings:before { .tgico-sendingerror:before {
content: "\e940"; content: "\e940";
} }
.tgico-edit:before { .tgico-sending:before {
content: "\e941"; content: "\e941";
} }
.tgico-download:before { .tgico-check:before {
content: "\e942"; content: "\e942";
} }
.tgico-cameraadd:before { .tgico-checks:before {
content: "\e943"; content: "\e943";
} }
.tgico-camera:before { .tgico-radioon:before {
content: "\e944"; content: "\e944";
} }
.tgico-permissions:before { .tgico-radiooff:before {
content: "\e945"; content: "\e945";
} }
.tgico-admin:before { .tgico-checkboxempty:before {
content: "\e946"; content: "\e946";
} }
.tgico-stop:before { .tgico-checkboxblock:before {
content: "\e947"; content: "\e947";
} }
.tgico-username:before { .tgico-checkboxon:before {
content: "\e948"; content: "\e948";
} }
.tgico-location:before { .tgico-eye2:before {
content: "\e949"; content: "\e949";
} }
.tgico-info:before { .tgico-eye1:before {
content: "\e94a"; content: "\e94a";
} }
.tgico-deleteuser:before { .tgico-FullScreen:before {
content: "\e94b"; content: "\e94b";
} }
.tgico-adduser:before { .tgico-smallscreen:before {
content: "\e94c"; content: "\e94c";
} }
.tgico-recent:before { .tgico-flag:before {
content: "\e94d"; content: "\e94d";
} }
.tgico-channel:before { .tgico-lamp:before {
content: "\e94e"; content: "\e94e";
} }
.tgico-document:before { .tgico-sport:before {
content: "\e94f"; content: "\e94f";
} }
.tgico-activesessions:before { .tgico-car:before {
content: "\e950"; content: "\e950";
} }
.tgico-logout:before { .tgico-eats:before {
content: "\e951"; content: "\e951";
} }
.tgico-help:before { .tgico-animals:before {
content: "\e952"; content: "\e952";
} }
.tgico-play:before { .tgico-smile:before {
content: "\e953"; content: "\e953";
} }
.tgico-pause:before { .tgico-unpin:before {
content: "\e954"; content: "\e954";
} }
.tgico-reply:before { .tgico-send:before {
content: "\e955"; content: "\e955";
} }
.tgico-forward:before { .tgico-unread:before {
content: "\e956"; content: "\e956";
} }
.tgico-next:before { .tgico-settings:before {
content: "\e957"; content: "\e957";
} }
.tgico-unlock:before { .tgico-edit:before {
content: "\e958"; content: "\e958";
} }
.tgico-lock:before { .tgico-download:before {
content: "\e959"; content: "\e959";
} }
.tgico-data:before { .tgico-cameraadd:before {
content: "\e95a"; content: "\e95a";
} }
.tgico-user:before { .tgico-camera:before {
content: "\e95b"; content: "\e95b";
} }
.tgico-group:before { .tgico-permissions:before {
content: "\e95c"; content: "\e95c";
} }
.tgico-mute:before { .tgico-admin:before {
content: "\e95d"; content: "\e95d";
} }
.tgico-unmute:before { .tgico-stop:before {
content: "\e95e"; content: "\e95e";
} }
.tgico-photo:before { .tgico-username:before {
content: "\e95f"; content: "\e95f";
} }
.tgico-language:before { .tgico-location:before {
content: "\e960"; content: "\e960";
} }
.tgico-message:before { .tgico-info:before {
content: "\e961"; content: "\e961";
} }
.tgico-pin:before { .tgico-deleteuser:before {
content: "\e962"; content: "\e962";
} }
.tgico-attach:before { .tgico-adduser:before {
content: "\e963"; content: "\e963";
} }
.tgico-phone:before { .tgico-recent:before {
content: "\e964"; content: "\e964";
} }
.tgico-savedmessages:before { .tgico-channel:before {
content: "\e965"; content: "\e965";
} }
.tgico-checkbox:before { .tgico-document:before {
content: "\e966"; content: "\e966";
} }
.tgico-copy:before { .tgico-activesessions:before {
content: "\e967"; content: "\e967";
} }
.tgico-unarchive:before { .tgico-logout:before {
content: "\e968"; content: "\e968";
} }
.tgico-archive:before { .tgico-help:before {
content: "\e969"; content: "\e969";
} }
.tgico-check1:before { .tgico-play:before {
content: "\e96a"; content: "\e96a";
} }
.tgico-up:before { .tgico-pause:before {
content: "\e96b"; content: "\e96b";
} }
.tgico-down:before { .tgico-reply:before {
content: "\e96c"; content: "\e96c";
} }
.tgico-close:before { .tgico-forward:before {
content: "\e96d"; content: "\e96d";
} }
.tgico-add:before { .tgico-next:before {
content: "\e96e"; content: "\e96e";
} }
.tgico-back:before { .tgico-unlock:before {
content: "\e96f"; content: "\e96f";
} }
.tgico-more:before { .tgico-lock:before {
content: "\e970"; content: "\e970";
} }
.tgico-menu:before { .tgico-data:before {
content: "\e971"; content: "\e971";
} }
.tgico-search:before { .tgico-user:before {
content: "\e972"; 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";
}

253
src/scss/partials/_ico.scss

@ -13,118 +13,141 @@ $tgico-font-path: "assets/fonts" !default;
.replace(/\.(.+?):before\{content:"(.+?);\}/g, `$$$1: "\\$2;\n`); .replace(/\.(.+?):before\{content:"(.+?);\}/g, `$$$1: "\\$2;\n`);
*/ */
$tgico-select: "\e900"; $tgico-dragmedia: "\e900";
$tgico-info2: "\e901"; $tgico-dragfiles: "\e901";
$tgico-clouddownload: "\e902"; $tgico-link: "\e902";
$tgico-readchats: "\e903"; $tgico-monospace: "\e903";
$tgico-noncontacts: "\e904"; $tgico-strikethrough: "\e904";
$tgico-bots: "\e905"; $tgico-underline: "\e905";
$tgico-muted: "\e906"; $tgico-italic: "\e906";
$tgico-favourites: "\e907"; $tgico-bold: "\e907";
$tgico-tip: "\e908"; $tgico-botcom: "\e908";
$tgico-loginlogodesktop: "\e909"; $tgico-calendarfilter: "\e909";
$tgico-loginlogomobile: "\e90a"; $tgico-email: "\e90a";
$tgico-calendar: "\e90b"; $tgico-passwordoff: "\e90b";
$tgico-keyboard: "\e90c"; $tgico-commentssticker: "\e90c";
$tgico-gifs: "\e90d"; $tgico-comments: "\e90d";
$tgico-stickers: "\e90e"; $tgico-previous: "\e90e";
$tgico-deleteleft: "\e90f"; $tgico-next: "\e90f";
$tgico-folder: "\e910"; $tgico-mention: "\e910";
$tgico-revote: "\e911"; $tgico-down: "\e911";
$tgico-livelocation: "\e912"; $tgico-pinlist: "\e912";
$tgico-microphone2: "\e913"; $tgico-replace: "\e913";
$tgico-colorize: "\e914"; $tgico-schedule: "\e914";
$tgico-poll: "\e915"; $tgico-zoomout: "\e915";
$tgico-minus: "\e916"; $tgico-zoomin: "\e916";
$tgico-nosound: "\e917"; $tgico-select: "\e917";
$tgico-microphone: "\e918"; $tgico-info2: "\e918";
$tgico-largeplay: "\e919"; $tgico-clouddownload: "\e919";
$tgico-largepause: "\e91a"; $tgico-readchats: "\e91a";
$tgico-newchannel: "\e91b"; $tgico-noncontacts: "\e91b";
$tgico-newgroup: "\e91c"; $tgico-bots: "\e91c";
$tgico-newprivate: "\e91d"; $tgico-muted: "\e91d";
$tgico-chatsplaceholder: "\e91e"; $tgico-favourites: "\e91e";
$tgico-newchat_filled: "\e91f"; $tgico-tip: "\e91f";
$tgico-addmember_filled: "\e920"; $tgico-loginlogodesktop: "\e920";
$tgico-delete: "\e921"; $tgico-loginlogomobile: "\e921";
$tgico-delete_filled: "\e922"; $tgico-calendar: "\e922";
$tgico-send2: "\e923"; $tgico-keyboard: "\e923";
$tgico-avatar_deletedaccount: "\e924"; $tgico-gifs: "\e924";
$tgico-avatar_archivedchats: "\e925"; $tgico-stickers: "\e925";
$tgico-avatar_savedmessages: "\e926"; $tgico-deleteleft: "\e926";
$tgico-pinnedchat: "\e927"; $tgico-folder: "\e927";
$tgico-channelviews: "\e928"; $tgico-revote: "\e928";
$tgico-sendingerror: "\e929"; $tgico-livelocation: "\e929";
$tgico-sending: "\e92a"; $tgico-microphone2: "\e92a";
$tgico-check: "\e92b"; $tgico-colorize: "\e92b";
$tgico-checks: "\e92c"; $tgico-poll: "\e92c";
$tgico-radioon: "\e92d"; $tgico-minus: "\e92d";
$tgico-radiooff: "\e92e"; $tgico-nosound: "\e92e";
$tgico-checkboxempty: "\e92f"; $tgico-microphone: "\e92f";
$tgico-checkboxblock: "\e930"; $tgico-largeplay: "\e930";
$tgico-checkboxon: "\e931"; $tgico-largepause: "\e931";
$tgico-eye2: "\e932"; $tgico-newchannel: "\e932";
$tgico-eye1: "\e933"; $tgico-newgroup: "\e933";
$tgico-fullscreen: "\e934"; $tgico-newprivate: "\e934";
$tgico-smallscreen: "\e935"; $tgico-chatsplaceholder: "\e935";
$tgico-flag: "\e936"; $tgico-newchat_filled: "\e936";
$tgico-lamp: "\e937"; $tgico-addmember_filled: "\e937";
$tgico-sport: "\e938"; $tgico-delete: "\e938";
$tgico-car: "\e939"; $tgico-delete_filled: "\e939";
$tgico-eats: "\e93a"; $tgico-send2: "\e93a";
$tgico-animals: "\e93b"; $tgico-avatar_deletedaccount: "\e93b";
$tgico-smile: "\e93c"; $tgico-avatar_archivedchats: "\e93c";
$tgico-unpin: "\e93d"; $tgico-avatar_savedmessages: "\e93d";
$tgico-send: "\e93e"; $tgico-pinnedchat: "\e93e";
$tgico-unread: "\e93f"; $tgico-channelviews: "\e93f";
$tgico-settings: "\e940"; $tgico-sendingerror: "\e940";
$tgico-edit: "\e941"; $tgico-sending: "\e941";
$tgico-download: "\e942"; $tgico-check: "\e942";
$tgico-cameraadd: "\e943"; $tgico-checks: "\e943";
$tgico-camera: "\e944"; $tgico-radioon: "\e944";
$tgico-permissions: "\e945"; $tgico-radiooff: "\e945";
$tgico-admin: "\e946"; $tgico-checkboxempty: "\e946";
$tgico-stop: "\e947"; $tgico-checkboxblock: "\e947";
$tgico-username: "\e948"; $tgico-checkboxon: "\e948";
$tgico-location: "\e949"; $tgico-eye2: "\e949";
$tgico-info: "\e94a"; $tgico-eye1: "\e94a";
$tgico-deleteuser: "\e94b"; $tgico-fullscreen: "\e94b";
$tgico-adduser: "\e94c"; $tgico-smallscreen: "\e94c";
$tgico-recent: "\e94d"; $tgico-flag: "\e94d";
$tgico-channel: "\e94e"; $tgico-lamp: "\e94e";
$tgico-document: "\e94f"; $tgico-sport: "\e94f";
$tgico-activesessions: "\e950"; $tgico-car: "\e950";
$tgico-logout: "\e951"; $tgico-eats: "\e951";
$tgico-help: "\e952"; $tgico-animals: "\e952";
$tgico-play: "\e953"; $tgico-smile: "\e953";
$tgico-pause: "\e954"; $tgico-unpin: "\e954";
$tgico-reply: "\e955"; $tgico-send: "\e955";
$tgico-forward: "\e956"; $tgico-unread: "\e956";
$tgico-next: "\e957"; $tgico-settings: "\e957";
$tgico-unlock: "\e958"; $tgico-edit: "\e958";
$tgico-lock: "\e959"; $tgico-download: "\e959";
$tgico-data: "\e95a"; $tgico-cameraadd: "\e95a";
$tgico-user: "\e95b"; $tgico-camera: "\e95b";
$tgico-group: "\e95c"; $tgico-permissions: "\e95c";
$tgico-mute: "\e95d"; $tgico-admin: "\e95d";
$tgico-unmute: "\e95e"; $tgico-stop: "\e95e";
$tgico-photo: "\e95f"; $tgico-username: "\e95f";
$tgico-language: "\e960"; $tgico-location: "\e960";
$tgico-message: "\e961"; $tgico-info: "\e961";
$tgico-pin: "\e962"; $tgico-deleteuser: "\e962";
$tgico-attach: "\e963"; $tgico-adduser: "\e963";
$tgico-phone: "\e964"; $tgico-recent: "\e964";
$tgico-savedmessages: "\e965"; $tgico-channel: "\e965";
$tgico-checkbox: "\e966"; $tgico-document: "\e966";
$tgico-copy: "\e967"; $tgico-activesessions: "\e967";
$tgico-unarchive: "\e968"; $tgico-logout: "\e968";
$tgico-archive: "\e969"; $tgico-help: "\e969";
$tgico-check1: "\e96a"; $tgico-play: "\e96a";
$tgico-up: "\e96b"; $tgico-pause: "\e96b";
$tgico-down: "\e96c"; $tgico-reply: "\e96c";
$tgico-close: "\e96d"; $tgico-forward: "\e96d";
$tgico-add: "\e96e"; $tgico-next: "\e96e";
$tgico-back: "\e96f"; $tgico-unlock: "\e96f";
$tgico-more: "\e970"; $tgico-lock: "\e970";
$tgico-menu: "\e971"; $tgico-data: "\e971";
$tgico-search: "\e972"; $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";

28
src/scss/partials/_input.scss

@ -153,3 +153,31 @@
input:focus, button:focus { input:focus, button:focus {
outline: none; 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);
}
}
Loading…
Cancel
Save