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. 463
      src/components/chat/input.ts
  2. 5
      src/components/inputField.ts
  3. 122
      src/helpers/dom.ts
  4. 10
      src/lib/appManagers/appImManager.ts
  5. 85
      src/lib/richtextprocessor.ts
  6. 97
      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

463
src/components/chat/input.ts

@ -11,7 +11,7 @@ import apiManager from "../../lib/mtproto/mtprotoworker"; @@ -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"; @@ -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 { @@ -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 { @@ -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 { @@ -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 <br>
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 { @@ -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 { @@ -605,6 +883,7 @@ export class ChatInput {
}
this.chatInput.classList.add('is-locked');
blurActiveElement();
this.recorder.start().then(() => {
this.recordCanceled = false;

5
src/components/inputField.ts

@ -18,11 +18,8 @@ let init = () => { @@ -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);

122
src/helpers/dom.ts

@ -1,5 +1,6 @@ @@ -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) { @@ -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 @@ -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 @@ -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 <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'; @@ -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 { @@ -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 { @@ -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) {

85
src/lib/richtextprocessor.ts

@ -6,6 +6,7 @@ import { MOUNT_CLASS_TO } from './mtproto/mtproto_config'; @@ -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 { @@ -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 { @@ -433,7 +434,8 @@ namespace RichTextProcessor {
' href="',
contextUrl.replace('{1}', encodeURIComponent(username)),
'">',
encodeEntities(entityText),
wrapRichNestedText(entityText, entity.nested, options),
//encodeEntities(entityText),
'</a>'
)
break;
@ -448,7 +450,7 @@ namespace RichTextProcessor { @@ -448,7 +450,7 @@ namespace RichTextProcessor {
'<a href="#/im?p=u',
encodeURIComponent(entity.user_id),
'">',
encodeEntities(entityText),
wrapRichNestedText(entityText, entity.nested, options),
'</a>'
);
break;
@ -492,7 +494,7 @@ namespace RichTextProcessor { @@ -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 { @@ -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, или же рендерить смайлик напрямую, без контейнера
`<span class="emoji">${encodeEntities(entityText)}</span>` :
`<img src="assets/img/emoji/${entity.unicode}.png" alt="${encodeEntities(entityText)}" class="emoji">`);
@ -554,29 +561,27 @@ namespace RichTextProcessor { @@ -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(`<span style="font-weight: bold;">${wrapRichNestedText(entityText, entity.nested, options)}</span>`);
} else {
html.push(`<strong>${wrapRichNestedText(entityText, entity.nested, options)}</strong>`);
}
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(`<span style="font-style: italic;">${wrapRichNestedText(entityText, entity.nested, options)}</span>`);
} else {
html.push(`<em>${wrapRichNestedText(entityText, entity.nested, options)}</em>`);
}
break;
}
@ -589,19 +594,21 @@ namespace RichTextProcessor { @@ -589,19 +594,21 @@ namespace RichTextProcessor {
break;
case 'messageEntityStrike':
html.push(
'<del>',
wrapRichNestedText(entityText, entity.nested, options),
'</del>'
);
if(options.wrappingDraft) {
const styleName = isSafari ? 'text-decoration' : 'text-decoration-line';
html.push(`<span style="${styleName}: line-through;">${wrapRichNestedText(entityText, entity.nested, options)}</span>`);
} else {
html.push(`<del>${wrapRichNestedText(entityText, entity.nested, options)}</del>`);
}
break;
case 'messageEntityUnderline':
html.push(
'<u>',
wrapRichNestedText(entityText, entity.nested, options),
'</u>'
);
if(options.wrappingDraft) {
const styleName = isSafari ? 'text-decoration' : 'text-decoration-line';
html.push(`<span style="${styleName}: underline;">${wrapRichNestedText(entityText, entity.nested, options)}</span>`);
} else {
html.push(`<u>${wrapRichNestedText(entityText, entity.nested, options)}</u>`);
}
break;
case 'messageEntityCode':
@ -610,11 +617,16 @@ namespace RichTextProcessor { @@ -610,11 +617,16 @@ namespace RichTextProcessor {
break;
}
html.push(
'<code>',
encodeEntities(entityText),
'</code>'
);
if(options.wrappingDraft) {
html.push(`<span style="font-family: monospace;">${encodeEntities(entityText)}</span>`);
} else {
html.push(
'<code>',
encodeEntities(entityText),
'</code>'
);
}
break;
case 'messageEntityPre':
@ -728,14 +740,11 @@ namespace RichTextProcessor { @@ -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
}

97
src/scss/partials/_chat.scss

@ -1324,4 +1324,99 @@ $chat-helper-size: 39px; @@ -1324,4 +1324,99 @@ $chat-helper-size: 39px;
border-bottom: 1px solid white;
}
}
}
}
.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 @@ @@ -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 @@ @@ -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";
}

253
src/scss/partials/_ico.scss

@ -13,118 +13,141 @@ $tgico-font-path: "assets/fonts" !default; @@ -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";

28
src/scss/partials/_input.scss

@ -152,4 +152,32 @@ @@ -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);
}
}
Loading…
Cancel
Save