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