Bot keyboard
Display 'via @username'
This commit is contained in:
parent
706bf9fd27
commit
01ec2005e9
@ -43,7 +43,7 @@ export default class BubbleGroups {
|
||||
|
||||
const timestamp = message.date;
|
||||
const mid = message.mid;
|
||||
let fromId = message.fromId;
|
||||
let fromId = message.viaBotId || message.fromId;
|
||||
let group: Group;
|
||||
|
||||
// fix for saved messages forward to self
|
||||
|
@ -2583,14 +2583,17 @@ export default class ChatBubbles {
|
||||
|
||||
let savedFrom = '';
|
||||
|
||||
const needName = (peerId < 0 && (peerId !== message.fromId || our)) && message.fromId !== rootScope.myId;
|
||||
const needName = ((peerId < 0 && (peerId !== message.fromId || our)) && message.fromId !== rootScope.myId) || message.viaBotId;
|
||||
if(needName || message.fwd_from || message.reply_to_mid) { // chat
|
||||
let title: HTMLSpanElement;
|
||||
|
||||
const isForwardFromChannel = message.from_id && message.from_id._ === 'peerChannel' && message.fromId === message.fwdFromId;
|
||||
|
||||
let isHidden = message.fwd_from && !message.fwd_from.from_id && !message.fwd_from.channel_id;
|
||||
if(isHidden) {
|
||||
if(message.viaBotId) {
|
||||
title = document.createElement('span');
|
||||
title.innerText = '@' + this.appUsersManager.getUser(message.viaBotId).username;
|
||||
} else if(isHidden) {
|
||||
///////this.log('message to render hidden', message);
|
||||
title = document.createElement('span');
|
||||
title.innerHTML = RichTextProcessor.wrapEmojiText(message.fwd_from.from_name);
|
||||
@ -2598,12 +2601,22 @@ export default class ChatBubbles {
|
||||
//title = message.fwd_from.from_name;
|
||||
bubble.classList.add('hidden-profile');
|
||||
} else {
|
||||
title = new PeerTitle({peerId: message.fwdFromId || message.fromId}).element;
|
||||
title = new PeerTitle({peerId: message.viaBotId || message.fwdFromId || message.fromId}).element;
|
||||
}
|
||||
|
||||
//this.log(title);
|
||||
|
||||
if((message.fwdFromId || message.fwd_from)) {
|
||||
if(message.viaBotId) {
|
||||
if(!bubble.classList.contains('sticker')) {
|
||||
let nameDiv = document.createElement('div');
|
||||
nameDiv.classList.add('name');
|
||||
nameDiv.dataset.peerId = message.viaBotId;
|
||||
nameDiv.append(i18n('ViaBot'), ' ', title);
|
||||
nameContainer.append(nameDiv);
|
||||
} else {
|
||||
bubble.classList.add('hide-name');
|
||||
}
|
||||
} else if((message.fwdFromId || message.fwd_from)) {
|
||||
if(this.peerId !== rootScope.myId && !isForwardFromChannel) {
|
||||
bubble.classList.add('forwarded');
|
||||
}
|
||||
|
@ -66,6 +66,7 @@ import AutocompleteHelper from './autocompleteHelper';
|
||||
import MentionsHelper from './mentionsHelper';
|
||||
import fixSafariStickyInput from '../../helpers/dom/fixSafariStickyInput';
|
||||
import { emojiFromCodePoints } from '../../vendor/emoji';
|
||||
import ReplyKeyboard from './replyKeyboard';
|
||||
|
||||
const RECORD_MIN_TIME = 500;
|
||||
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
|
||||
@ -88,8 +89,11 @@ export default class ChatInput {
|
||||
public rowsWrapper: HTMLDivElement;
|
||||
private newMessageWrapper: HTMLDivElement;
|
||||
private btnToggleEmoticons: HTMLButtonElement;
|
||||
private btnToggleReplyMarkup: HTMLButtonElement;
|
||||
private btnSendContainer: HTMLDivElement;
|
||||
|
||||
private replyKeyboard: ReplyKeyboard;
|
||||
|
||||
private attachMenu: HTMLButtonElement;
|
||||
private attachMenuButtons: (ButtonMenuItemOptions & {verify: (peerId: number) => boolean})[];
|
||||
|
||||
@ -294,8 +298,7 @@ export default class ChatInput {
|
||||
this.goDownUnreadBadge.classList.add('badge', 'badge-24', 'badge-primary');
|
||||
this.goDownBtn.append(this.goDownUnreadBadge);
|
||||
|
||||
this.btnScheduled = ButtonIcon('scheduled', {noRipple: true});
|
||||
this.btnScheduled.classList.add('btn-scheduled', 'hide');
|
||||
this.btnScheduled = ButtonIcon('scheduled btn-scheduled float hide', {noRipple: true});
|
||||
|
||||
attachClickEvent(this.btnScheduled, (e) => {
|
||||
this.appImManager.openScheduled(this.chat.peerId);
|
||||
@ -322,6 +325,16 @@ export default class ChatInput {
|
||||
this.btnScheduled.classList.toggle('hide', !value.length);
|
||||
});
|
||||
});
|
||||
|
||||
this.btnToggleReplyMarkup = ButtonIcon('botcom toggle-reply-markup float hide', {noRipple: true});
|
||||
this.replyKeyboard = new ReplyKeyboard({
|
||||
appendTo: this.rowsWrapper,
|
||||
listenerSetter: this.listenerSetter,
|
||||
appMessagesManager: this.appMessagesManager,
|
||||
btnHover: this.btnToggleReplyMarkup
|
||||
});
|
||||
this.listenerSetter.add(this.replyKeyboard, 'open', () => this.btnToggleReplyMarkup.classList.add('active'));
|
||||
this.listenerSetter.add(this.replyKeyboard, 'close', () => this.btnToggleReplyMarkup.classList.remove('active'));
|
||||
}
|
||||
|
||||
this.attachMenuButtons = [{
|
||||
@ -367,7 +380,7 @@ export default class ChatInput {
|
||||
this.fileInput.multiple = true;
|
||||
this.fileInput.style.display = 'none';
|
||||
|
||||
this.newMessageWrapper.append(...[this.btnToggleEmoticons, this.inputMessageContainer, this.btnScheduled, this.attachMenu, this.recordTimeEl, this.fileInput].filter(Boolean));
|
||||
this.newMessageWrapper.append(...[this.btnToggleEmoticons, this.inputMessageContainer, this.btnScheduled, this.btnToggleReplyMarkup, this.attachMenu, this.recordTimeEl, this.fileInput].filter(Boolean));
|
||||
|
||||
this.rowsWrapper.append(this.replyElements.container);
|
||||
this.autocompleteHelperController = new AutocompleteHelperController();
|
||||
@ -418,8 +431,8 @@ export default class ChatInput {
|
||||
this.inputContainer.append(this.btnCancelRecord, this.btnSendContainer);
|
||||
|
||||
emoticonsDropdown.attachButtonListener(this.btnToggleEmoticons, this.listenerSetter);
|
||||
emoticonsDropdown.events.onOpen.push(this.onEmoticonsOpen);
|
||||
emoticonsDropdown.events.onClose.push(this.onEmoticonsClose);
|
||||
this.listenerSetter.add(emoticonsDropdown, 'open', this.onEmoticonsOpen);
|
||||
this.listenerSetter.add(emoticonsDropdown, 'close', this.onEmoticonsClose);
|
||||
|
||||
this.attachMessageInputField();
|
||||
|
||||
@ -651,9 +664,6 @@ export default class ChatInput {
|
||||
public destroy() {
|
||||
//this.chat.log.error('Input destroying');
|
||||
|
||||
emoticonsDropdown.events.onOpen.findAndSplice(f => f === this.onEmoticonsOpen);
|
||||
emoticonsDropdown.events.onClose.findAndSplice(f => f === this.onEmoticonsClose);
|
||||
|
||||
this.listenerSetter.removeAll();
|
||||
}
|
||||
|
||||
@ -720,6 +730,10 @@ export default class ChatInput {
|
||||
});
|
||||
}
|
||||
|
||||
if(this.replyKeyboard) {
|
||||
this.replyKeyboard.setPeer(peerId);
|
||||
}
|
||||
|
||||
if(this.sendMenu) {
|
||||
this.sendMenu.setPeerId(peerId);
|
||||
}
|
||||
@ -1479,6 +1493,10 @@ export default class ChatInput {
|
||||
if(this.btnScheduled) {
|
||||
this.btnScheduled.classList.toggle('show', isInputEmpty);
|
||||
}
|
||||
|
||||
if(this.btnToggleReplyMarkup) {
|
||||
this.btnToggleReplyMarkup.classList.toggle('show', isInputEmpty);
|
||||
}
|
||||
}
|
||||
|
||||
public onMessageSent(clearInput = true, clearReply?: boolean) {
|
||||
|
108
src/components/chat/replyKeyboard.ts
Normal file
108
src/components/chat/replyKeyboard.ts
Normal file
@ -0,0 +1,108 @@
|
||||
/*
|
||||
* https://github.com/morethanwords/tweb
|
||||
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
|
||||
import DropdownHover from "../../helpers/dropdownHover";
|
||||
import { ReplyMarkup } from "../../layer";
|
||||
import RichTextProcessor from "../../lib/richtextprocessor";
|
||||
import rootScope from "../../lib/rootScope";
|
||||
import { safeAssign } from "../../helpers/object";
|
||||
import ListenerSetter from "../../helpers/listenerSetter";
|
||||
import findUpClassName from "../../helpers/dom/findUpClassName";
|
||||
|
||||
export default class ReplyKeyboard extends DropdownHover {
|
||||
private static BASE_CLASS = 'reply-keyboard';
|
||||
private appendTo: HTMLElement;
|
||||
private listenerSetter: ListenerSetter;
|
||||
private appMessagesManager: AppMessagesManager;
|
||||
private btnHover: HTMLElement;
|
||||
private peerId: number;
|
||||
|
||||
constructor(options: {
|
||||
listenerSetter: ListenerSetter,
|
||||
appMessagesManager: AppMessagesManager,
|
||||
appendTo: HTMLElement,
|
||||
btnHover: HTMLElement
|
||||
}) {
|
||||
super({
|
||||
element: document.createElement('div')
|
||||
});
|
||||
|
||||
safeAssign(this, options);
|
||||
|
||||
this.element.classList.add(ReplyKeyboard.BASE_CLASS);
|
||||
this.element.style.display = 'none';
|
||||
|
||||
this.attachButtonListener(this.btnHover, this.listenerSetter);
|
||||
this.listenerSetter.add(rootScope, 'history_reply_markup', ({peerId}) => {
|
||||
if(this.peerId === peerId && this.checkAvailability() && this.isActive()) {
|
||||
this.render();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected init() {
|
||||
this.appendTo.append(this.element);
|
||||
|
||||
this.listenerSetter.add(this, 'open', () => {
|
||||
this.render();
|
||||
});
|
||||
|
||||
this.listenerSetter.add(this.element, 'click', (e) => {
|
||||
const target = findUpClassName(e.target, 'btn');
|
||||
if(!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.appMessagesManager.sendText(this.peerId, target.dataset.text);
|
||||
this.toggle(false);
|
||||
});
|
||||
|
||||
return super.init();
|
||||
}
|
||||
|
||||
private getReplyMarkup(): ReplyMarkup {
|
||||
return this.appMessagesManager.getHistoryStorage(this.peerId).reply_markup ?? {
|
||||
_: 'replyKeyboardHide'
|
||||
};
|
||||
}
|
||||
|
||||
public render(replyMarkup: ReplyMarkup.replyKeyboardMarkup = this.getReplyMarkup() as any) {
|
||||
this.element.innerHTML = '';
|
||||
|
||||
for(const row of replyMarkup.rows) {
|
||||
const div = document.createElement('div');
|
||||
div.classList.add(ReplyKeyboard.BASE_CLASS + '-row');
|
||||
|
||||
for(const button of row.buttons) {
|
||||
const btn = document.createElement('button');
|
||||
btn.classList.add(ReplyKeyboard.BASE_CLASS + '-button', 'btn');
|
||||
btn.innerHTML = RichTextProcessor.wrapEmojiText(button.text);
|
||||
btn.dataset.text = button.text;
|
||||
div.append(btn);
|
||||
}
|
||||
|
||||
this.element.append(div);
|
||||
}
|
||||
}
|
||||
|
||||
public checkAvailability(replyMarkup: ReplyMarkup = this.getReplyMarkup()) {
|
||||
const hide = replyMarkup._ === 'replyKeyboardHide';
|
||||
this.btnHover.classList.toggle('hide', hide);
|
||||
|
||||
if(hide) {
|
||||
this.toggle(false);
|
||||
}
|
||||
|
||||
return !hide;
|
||||
}
|
||||
|
||||
public setPeer(peerId: number) {
|
||||
this.peerId = peerId;
|
||||
|
||||
this.checkAvailability();
|
||||
}
|
||||
}
|
@ -23,11 +23,10 @@ import AppGifsTab from "../sidebarRight/tabs/gifs";
|
||||
import AppStickersTab from "../sidebarRight/tabs/stickers";
|
||||
import findUpClassName from "../../helpers/dom/findUpClassName";
|
||||
import findUpTag from "../../helpers/dom/findUpTag";
|
||||
import ListenerSetter from "../../helpers/listenerSetter";
|
||||
import blurActiveElement from "../../helpers/dom/blurActiveElement";
|
||||
import { attachClickEvent } from "../../helpers/dom/clickEvent";
|
||||
import whichChild from "../../helpers/dom/whichChild";
|
||||
import { cancelEvent } from "../../helpers/dom/cancelEvent";
|
||||
import DropdownHover from "../../helpers/dropdownHover";
|
||||
|
||||
export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown';
|
||||
|
||||
@ -36,13 +35,8 @@ export interface EmoticonsTab {
|
||||
onCloseAfterTimeout?: () => void
|
||||
}
|
||||
|
||||
const KEEP_OPEN = false;
|
||||
const TOGGLE_TIMEOUT = 200;
|
||||
const ANIMATION_DURATION = 200;
|
||||
|
||||
export class EmoticonsDropdown {
|
||||
export class EmoticonsDropdown extends DropdownHover {
|
||||
public static lazyLoadQueue = new LazyLoadQueue();
|
||||
private element: HTMLElement;
|
||||
|
||||
private emojiTab: EmojiTab;
|
||||
public stickersTab: StickersTab;
|
||||
@ -56,73 +50,70 @@ export class EmoticonsDropdown {
|
||||
|
||||
private searchButton: HTMLElement;
|
||||
private deleteBtn: HTMLElement;
|
||||
|
||||
private displayTimeout: number;
|
||||
|
||||
public events: {
|
||||
onClose: Array<() => void>,
|
||||
onCloseAfter: Array<() => void>,
|
||||
onOpen: Array<() => void>,
|
||||
onOpenAfter: Array<() => void>
|
||||
} = {
|
||||
onClose: [],
|
||||
onCloseAfter: [],
|
||||
onOpen: [],
|
||||
onOpenAfter: []
|
||||
};
|
||||
|
||||
private selectTab: ReturnType<typeof horizontalMenu>;
|
||||
private forceClose = false;
|
||||
|
||||
private savedRange: Range;
|
||||
|
||||
constructor() {
|
||||
this.element = document.getElementById('emoji-dropdown') as HTMLDivElement;
|
||||
super({
|
||||
element: document.getElementById('emoji-dropdown') as HTMLDivElement
|
||||
});
|
||||
|
||||
this.addEventListener('open', async() => {
|
||||
if(isTouchSupported) {
|
||||
//appImManager.chat.input.saveScroll();
|
||||
if(blurActiveElement()) {
|
||||
await pause(100);
|
||||
}
|
||||
}
|
||||
|
||||
if(this.element.parentElement !== appImManager.chat.input.chatInput) {
|
||||
appImManager.chat.input.chatInput.append(this.element);
|
||||
}
|
||||
|
||||
const sel = document.getSelection();
|
||||
if(sel.rangeCount && document.activeElement === appImManager.chat.input.messageInput) {
|
||||
this.savedRange = sel.getRangeAt(0);
|
||||
} else {
|
||||
this.savedRange = undefined;
|
||||
}
|
||||
|
||||
EmoticonsDropdown.lazyLoadQueue.lock();
|
||||
//EmoticonsDropdown.lazyLoadQueue.unlock();
|
||||
animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP);
|
||||
});
|
||||
|
||||
this.addEventListener('opened', () => {
|
||||
animationIntersector.unlockIntersectionGroup(EMOTICONSSTICKERGROUP);
|
||||
EmoticonsDropdown.lazyLoadQueue.unlock();
|
||||
EmoticonsDropdown.lazyLoadQueue.refresh();
|
||||
|
||||
this.container.classList.remove('disable-hover');
|
||||
});
|
||||
|
||||
this.addEventListener('close', () => {
|
||||
EmoticonsDropdown.lazyLoadQueue.lock();
|
||||
//EmoticonsDropdown.lazyLoadQueue.lock();
|
||||
|
||||
// нужно залочить группу и выключить стикеры
|
||||
animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP);
|
||||
animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP);
|
||||
});
|
||||
|
||||
this.addEventListener('closed', () => {
|
||||
// теперь можно убрать visible, чтобы они не включились после фокуса
|
||||
animationIntersector.unlockIntersectionGroup(EMOTICONSSTICKERGROUP);
|
||||
EmoticonsDropdown.lazyLoadQueue.unlock();
|
||||
EmoticonsDropdown.lazyLoadQueue.refresh();
|
||||
|
||||
this.container.classList.remove('disable-hover');
|
||||
|
||||
this.savedRange = undefined;
|
||||
});
|
||||
}
|
||||
|
||||
public attachButtonListener(button: HTMLElement, listenerSetter: ListenerSetter) {
|
||||
let firstTime = true;
|
||||
if(isTouchSupported) {
|
||||
attachClickEvent(button, () => {
|
||||
if(firstTime) {
|
||||
firstTime = false;
|
||||
this.toggle(true);
|
||||
} else {
|
||||
this.toggle();
|
||||
}
|
||||
}, {listenerSetter});
|
||||
} else {
|
||||
listenerSetter.add(button, 'mouseover', (e) => {
|
||||
//console.log('onmouseover button');
|
||||
if(firstTime) {
|
||||
listenerSetter.add(button, 'mouseout', this.onMouseOut);
|
||||
firstTime = false;
|
||||
}
|
||||
|
||||
clearTimeout(this.displayTimeout);
|
||||
this.displayTimeout = window.setTimeout(() => {
|
||||
this.toggle(true);
|
||||
}, TOGGLE_TIMEOUT);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onMouseOut = (e: MouseEvent) => {
|
||||
if(KEEP_OPEN) return;
|
||||
clearTimeout(this.displayTimeout);
|
||||
if(!this.element.classList.contains('active')) return;
|
||||
|
||||
const toElement = (e as any).toElement as Element;
|
||||
if(toElement && findUpClassName(toElement, 'emoji-dropdown')) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.displayTimeout = window.setTimeout(() => {
|
||||
this.toggle(false);
|
||||
}, TOGGLE_TIMEOUT);
|
||||
};
|
||||
|
||||
private init() {
|
||||
protected init() {
|
||||
this.emojiTab = new EmojiTab();
|
||||
this.stickersTab = new StickersTab();
|
||||
this.gifsTab = new GifsTab();
|
||||
@ -186,17 +177,7 @@ export class EmoticonsDropdown {
|
||||
rootScope.addEventListener('peer_changed', this.checkRights);
|
||||
this.checkRights();
|
||||
|
||||
if(!isTouchSupported) {
|
||||
this.element.onmouseout = this.onMouseOut;
|
||||
this.element.onmouseover = (e) => {
|
||||
if(this.forceClose) {
|
||||
return;
|
||||
}
|
||||
|
||||
//console.log('onmouseover element');
|
||||
clearTimeout(this.displayTimeout);
|
||||
};
|
||||
}
|
||||
return super.init();
|
||||
}
|
||||
|
||||
private onSelectTabClick = (id: number) => {
|
||||
@ -228,116 +209,6 @@ export class EmoticonsDropdown {
|
||||
}
|
||||
};
|
||||
|
||||
public toggle = async(enable?: boolean) => {
|
||||
//if(!this.element) return;
|
||||
const willBeActive = (!!this.element.style.display && enable === undefined) || enable;
|
||||
if(this.init) {
|
||||
if(willBeActive) {
|
||||
this.init();
|
||||
this.init = null;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(isTouchSupported) {
|
||||
if(willBeActive) {
|
||||
//appImManager.chat.input.saveScroll();
|
||||
if(blurActiveElement()) {
|
||||
await pause(100);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(this.element.parentElement !== appImManager.chat.input.chatInput) {
|
||||
appImManager.chat.input.chatInput.append(this.element);
|
||||
}
|
||||
|
||||
if((this.element.style.display && enable === undefined) || enable) {
|
||||
this.events.onOpen.forEach(cb => cb());
|
||||
|
||||
const sel = document.getSelection();
|
||||
if(sel.rangeCount && document.activeElement === appImManager.chat.input.messageInput) {
|
||||
this.savedRange = sel.getRangeAt(0);
|
||||
} else {
|
||||
this.savedRange = undefined;
|
||||
}
|
||||
|
||||
EmoticonsDropdown.lazyLoadQueue.lock();
|
||||
//EmoticonsDropdown.lazyLoadQueue.unlock();
|
||||
animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP);
|
||||
|
||||
this.element.style.display = '';
|
||||
void this.element.offsetLeft; // reflow
|
||||
this.element.classList.add('active');
|
||||
|
||||
clearTimeout(this.displayTimeout);
|
||||
this.displayTimeout = window.setTimeout(() => {
|
||||
animationIntersector.unlockIntersectionGroup(EMOTICONSSTICKERGROUP);
|
||||
EmoticonsDropdown.lazyLoadQueue.unlock();
|
||||
EmoticonsDropdown.lazyLoadQueue.refresh();
|
||||
|
||||
this.forceClose = false;
|
||||
this.container.classList.remove('disable-hover');
|
||||
|
||||
this.events.onOpenAfter.forEach(cb => cb());
|
||||
}, isTouchSupported ? 0 : ANIMATION_DURATION);
|
||||
|
||||
// ! can't use together with resizeObserver
|
||||
/* if(isTouchSupported) {
|
||||
const height = this.element.scrollHeight + appImManager.chat.input.inputContainer.scrollHeight - 10;
|
||||
console.log('[ESG]: toggle: enable height', height);
|
||||
appImManager.chat.bubbles.scrollable.scrollTop += height;
|
||||
} */
|
||||
|
||||
/* if(touchSupport) {
|
||||
this.restoreScroll();
|
||||
} */
|
||||
} else {
|
||||
this.events.onClose.forEach(cb => cb());
|
||||
|
||||
EmoticonsDropdown.lazyLoadQueue.lock();
|
||||
//EmoticonsDropdown.lazyLoadQueue.lock();
|
||||
|
||||
// нужно залочить группу и выключить стикеры
|
||||
animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP);
|
||||
animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP);
|
||||
|
||||
this.element.classList.remove('active');
|
||||
|
||||
clearTimeout(this.displayTimeout);
|
||||
this.displayTimeout = window.setTimeout(() => {
|
||||
this.element.style.display = 'none';
|
||||
|
||||
// теперь можно убрать visible, чтобы они не включились после фокуса
|
||||
animationIntersector.unlockIntersectionGroup(EMOTICONSSTICKERGROUP);
|
||||
EmoticonsDropdown.lazyLoadQueue.unlock();
|
||||
EmoticonsDropdown.lazyLoadQueue.refresh();
|
||||
|
||||
this.forceClose = false;
|
||||
this.container.classList.remove('disable-hover');
|
||||
|
||||
this.events.onCloseAfter.forEach(cb => cb());
|
||||
|
||||
this.savedRange = undefined;
|
||||
}, isTouchSupported ? 0 : ANIMATION_DURATION);
|
||||
|
||||
/* if(isTouchSupported) {
|
||||
const scrollHeight = this.container.scrollHeight;
|
||||
if(scrollHeight) {
|
||||
const height = this.container.scrollHeight + appImManager.chat.input.inputContainer.scrollHeight - 10;
|
||||
appImManager.chat.bubbles.scrollable.scrollTop -= height;
|
||||
}
|
||||
} */
|
||||
|
||||
/* if(touchSupport) {
|
||||
this.restoreScroll();
|
||||
} */
|
||||
}
|
||||
|
||||
//animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP);
|
||||
};
|
||||
|
||||
public static menuOnClick = (menu: HTMLElement, scroll: Scrollable, menuScroll?: ScrollableX) => {
|
||||
let prevId = 0;
|
||||
let jumpedTo = -1;
|
||||
@ -431,11 +302,11 @@ export class EmoticonsDropdown {
|
||||
};
|
||||
|
||||
public addLazyLoadQueueRepeat(lazyLoadQueue: LazyLoadQueueIntersector, processInvisibleDiv: (div: HTMLElement) => void) {
|
||||
this.events.onClose.push(() => {
|
||||
this.addEventListener('close', () => {
|
||||
lazyLoadQueue.lock();
|
||||
});
|
||||
|
||||
this.events.onCloseAfter.push(() => {
|
||||
this.addEventListener('closed', () => {
|
||||
const divs = lazyLoadQueue.intersector.getVisible();
|
||||
|
||||
for(const div of divs) {
|
||||
@ -445,7 +316,7 @@ export class EmoticonsDropdown {
|
||||
lazyLoadQueue.intersector.clearVisible();
|
||||
});
|
||||
|
||||
this.events.onOpenAfter.push(() => {
|
||||
this.addEventListener('opened', () => {
|
||||
lazyLoadQueue.unlockAndRefresh();
|
||||
});
|
||||
}
|
||||
|
3
src/helpers/assumeType.ts
Normal file
3
src/helpers/assumeType.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export default function assumeType<T>(x: unknown): asserts x is T {
|
||||
return; // ¯\_(ツ)_/¯
|
||||
}
|
163
src/helpers/dropdownHover.ts
Normal file
163
src/helpers/dropdownHover.ts
Normal file
@ -0,0 +1,163 @@
|
||||
/*
|
||||
* https://github.com/morethanwords/tweb
|
||||
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
import { attachClickEvent } from "./dom/clickEvent";
|
||||
import findUpAsChild from "./dom/findUpAsChild";
|
||||
import EventListenerBase from "./eventListenerBase";
|
||||
import ListenerSetter from "./listenerSetter";
|
||||
import { safeAssign } from "./object";
|
||||
import { isTouchSupported } from "./touchSupport";
|
||||
|
||||
const KEEP_OPEN = false;
|
||||
const TOGGLE_TIMEOUT = 200;
|
||||
const ANIMATION_DURATION = 200;
|
||||
|
||||
export default class DropdownHover extends EventListenerBase<{
|
||||
open: () => Promise<any> | void,
|
||||
opened: () => any,
|
||||
close: () => any,
|
||||
closed: () => any
|
||||
}> {
|
||||
protected element: HTMLElement;
|
||||
protected displayTimeout: number;
|
||||
protected forceClose = false;
|
||||
protected inited = false;
|
||||
|
||||
constructor(options: {
|
||||
element: DropdownHover['element']
|
||||
}) {
|
||||
super(false);
|
||||
safeAssign(this, options);
|
||||
}
|
||||
|
||||
public attachButtonListener(button: HTMLElement, listenerSetter: ListenerSetter) {
|
||||
let firstTime = true;
|
||||
if(isTouchSupported) {
|
||||
attachClickEvent(button, () => {
|
||||
if(firstTime) {
|
||||
firstTime = false;
|
||||
this.toggle(true);
|
||||
} else {
|
||||
this.toggle();
|
||||
}
|
||||
}, {listenerSetter});
|
||||
} else {
|
||||
listenerSetter.add(button, 'mouseover', (e) => {
|
||||
//console.log('onmouseover button');
|
||||
if(firstTime) {
|
||||
listenerSetter.add(button, 'mouseout', this.onMouseOut);
|
||||
firstTime = false;
|
||||
}
|
||||
|
||||
clearTimeout(this.displayTimeout);
|
||||
this.displayTimeout = window.setTimeout(() => {
|
||||
this.toggle(true);
|
||||
}, TOGGLE_TIMEOUT);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private onMouseOut = (e: MouseEvent) => {
|
||||
if(KEEP_OPEN) return;
|
||||
clearTimeout(this.displayTimeout);
|
||||
if(!this.isActive()) return;
|
||||
|
||||
const toElement = (e as any).toElement as Element;
|
||||
if(toElement && findUpAsChild(toElement, this.element)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.displayTimeout = window.setTimeout(() => {
|
||||
this.toggle(false);
|
||||
}, TOGGLE_TIMEOUT);
|
||||
};
|
||||
|
||||
protected init() {
|
||||
if(!isTouchSupported) {
|
||||
this.element.onmouseout = this.onMouseOut;
|
||||
this.element.onmouseover = (e) => {
|
||||
if(this.forceClose) {
|
||||
return;
|
||||
}
|
||||
|
||||
//console.log('onmouseover element');
|
||||
clearTimeout(this.displayTimeout);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public toggle = async(enable?: boolean) => {
|
||||
//if(!this.element) return;
|
||||
const willBeActive = (!!this.element.style.display && enable === undefined) || enable;
|
||||
if(this.init) {
|
||||
if(willBeActive) {
|
||||
this.init();
|
||||
this.init = null;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(willBeActive === this.isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if((this.element.style.display && enable === undefined) || enable) {
|
||||
const res = this.dispatchEvent('open');
|
||||
await Promise.all(res);
|
||||
|
||||
this.element.style.display = '';
|
||||
void this.element.offsetLeft; // reflow
|
||||
this.element.classList.add('active');
|
||||
|
||||
clearTimeout(this.displayTimeout);
|
||||
this.displayTimeout = window.setTimeout(() => {
|
||||
this.forceClose = false;
|
||||
this.dispatchEvent('opened');
|
||||
}, isTouchSupported ? 0 : ANIMATION_DURATION);
|
||||
|
||||
// ! can't use together with resizeObserver
|
||||
/* if(isTouchSupported) {
|
||||
const height = this.element.scrollHeight + appImManager.chat.input.inputContainer.scrollHeight - 10;
|
||||
console.log('[ESG]: toggle: enable height', height);
|
||||
appImManager.chat.bubbles.scrollable.scrollTop += height;
|
||||
} */
|
||||
|
||||
/* if(touchSupport) {
|
||||
this.restoreScroll();
|
||||
} */
|
||||
} else {
|
||||
this.dispatchEvent('close');
|
||||
|
||||
this.element.classList.remove('active');
|
||||
|
||||
clearTimeout(this.displayTimeout);
|
||||
this.displayTimeout = window.setTimeout(() => {
|
||||
this.element.style.display = 'none';
|
||||
this.forceClose = false;
|
||||
this.dispatchEvent('closed');
|
||||
}, isTouchSupported ? 0 : ANIMATION_DURATION);
|
||||
|
||||
/* if(isTouchSupported) {
|
||||
const scrollHeight = this.container.scrollHeight;
|
||||
if(scrollHeight) {
|
||||
const height = this.container.scrollHeight + appImManager.chat.input.inputContainer.scrollHeight - 10;
|
||||
appImManager.chat.bubbles.scrollable.scrollTop -= height;
|
||||
}
|
||||
} */
|
||||
|
||||
/* if(touchSupport) {
|
||||
this.restoreScroll();
|
||||
} */
|
||||
}
|
||||
|
||||
//animationIntersector.checkAnimations(false, EMOTICONSSTICKERGROUP);
|
||||
};
|
||||
|
||||
public isActive() {
|
||||
return this.element.classList.contains('active');
|
||||
}
|
||||
}
|
@ -483,6 +483,7 @@ const lang = {
|
||||
"JoinByPeekChannelTitle": "Join Channel",
|
||||
"JoinByPeekGroupTitle": "Join Group",
|
||||
"YouWereKicked": "you were removed",
|
||||
"ViaBot": "via",
|
||||
|
||||
// * macos
|
||||
"AccountSettings.Filters": "Chat Folders",
|
||||
|
19
src/layer.d.ts
vendored
19
src/layer.d.ts
vendored
@ -857,7 +857,8 @@ export namespace Message {
|
||||
peerId?: number,
|
||||
fromId?: number,
|
||||
random_id?: string,
|
||||
rReply?: string
|
||||
rReply?: string,
|
||||
viaBotId?: number
|
||||
};
|
||||
|
||||
export type messageService = {
|
||||
@ -884,7 +885,8 @@ export namespace Message {
|
||||
deleted?: boolean,
|
||||
peerId?: number,
|
||||
fromId?: number,
|
||||
rReply?: string
|
||||
rReply?: string,
|
||||
viaBotId?: number
|
||||
};
|
||||
}
|
||||
|
||||
@ -3995,7 +3997,8 @@ export namespace ReplyMarkup {
|
||||
flags?: number,
|
||||
pFlags?: Partial<{
|
||||
selective?: true,
|
||||
}>
|
||||
}>,
|
||||
mid?: number
|
||||
};
|
||||
|
||||
export type replyKeyboardForceReply = {
|
||||
@ -4004,7 +4007,10 @@ export namespace ReplyMarkup {
|
||||
pFlags?: Partial<{
|
||||
single_use?: true,
|
||||
selective?: true,
|
||||
}>
|
||||
hidden?: true,
|
||||
}>,
|
||||
mid?: number,
|
||||
fromId?: number
|
||||
};
|
||||
|
||||
export type replyKeyboardMarkup = {
|
||||
@ -4014,8 +4020,11 @@ export namespace ReplyMarkup {
|
||||
resize?: true,
|
||||
single_use?: true,
|
||||
selective?: true,
|
||||
hidden?: true,
|
||||
}>,
|
||||
rows: Array<KeyboardButtonRow>
|
||||
rows: Array<KeyboardButtonRow>,
|
||||
mid?: number,
|
||||
fromId?: number
|
||||
};
|
||||
|
||||
export type replyInlineMarkup = {
|
||||
|
@ -17,7 +17,7 @@ import { createPosterForVideo } from "../../helpers/files";
|
||||
import { copy, getObjectKeysAndSort } from "../../helpers/object";
|
||||
import { randomLong } from "../../helpers/random";
|
||||
import { splitStringByLength, limitSymbols, escapeRegExp } from "../../helpers/string";
|
||||
import { Chat, ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageMedia, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MethodDeclMap, NotifyPeer, PeerNotifySettings, PhotoSize, SendMessageAction, Update, Photo, Updates } from "../../layer";
|
||||
import { Chat, ChatFull, Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMedia, InputMessage, InputPeerNotifySettings, InputSingleMedia, Message, MessageAction, MessageEntity, MessageFwdHeader, MessageMedia, MessageReplies, MessageReplyHeader, MessagesDialogs, MessagesFilter, MessagesMessages, MethodDeclMap, NotifyPeer, PeerNotifySettings, PhotoSize, SendMessageAction, Update, Photo, Updates, ReplyMarkup } from "../../layer";
|
||||
import { InvokeApiOptions } from "../../types";
|
||||
import I18n, { i18n, join, langPack, LangPackKey, _i18n } from "../langPack";
|
||||
import { logger, LogTypes } from "../logger";
|
||||
@ -56,6 +56,7 @@ import formatCallDuration from "../../helpers/formatCallDuration";
|
||||
import appAvatarsManager from "./appAvatarsManager";
|
||||
import telegramMeWebManager from "../mtproto/telegramMeWebManager";
|
||||
import { getMiddleware } from "../../helpers/middleware";
|
||||
import assumeType from "../../helpers/assumeType";
|
||||
|
||||
//console.trace('include');
|
||||
// TODO: если удалить сообщение в непрогруженном диалоге, то при обновлении, из-за стейта, последнего сообщения в чатлисте не будет
|
||||
@ -74,7 +75,7 @@ export type HistoryStorage = {
|
||||
triedToReadMaxId?: number,
|
||||
|
||||
maxOutId?: number,
|
||||
reply_markup?: any
|
||||
reply_markup?: Exclude<ReplyMarkup, ReplyMarkup.replyInlineMarkup>
|
||||
};
|
||||
|
||||
export type HistoryResult = {
|
||||
@ -3025,19 +3026,24 @@ export class AppMessagesManager {
|
||||
) && !message.pFlags.is_outgoing;
|
||||
}
|
||||
|
||||
public mergeReplyKeyboard(historyStorage: HistoryStorage, message: any) {
|
||||
public getReplyKeyboard(peerId: number) {
|
||||
return this.getHistoryStorage(peerId).reply_markup;
|
||||
}
|
||||
|
||||
public mergeReplyKeyboard(historyStorage: HistoryStorage, message: Message.messageService | Message.message) {
|
||||
// this.log('merge', message.mid, message.reply_markup, historyStorage.reply_markup)
|
||||
if(!message.reply_markup &&
|
||||
let messageReplyMarkup = (message as Message.message).reply_markup;
|
||||
if(!messageReplyMarkup &&
|
||||
!message.pFlags?.out &&
|
||||
!message.action) {
|
||||
!(message as Message.messageService).action) {
|
||||
return false;
|
||||
}
|
||||
if(message.reply_markup &&
|
||||
message.reply_markup._ === 'replyInlineMarkup') {
|
||||
|
||||
if(messageReplyMarkup?._ === 'replyInlineMarkup') {
|
||||
return false;
|
||||
}
|
||||
var messageReplyMarkup = message.reply_markup;
|
||||
var lastReplyMarkup = historyStorage.reply_markup;
|
||||
|
||||
const lastReplyMarkup = historyStorage.reply_markup;
|
||||
if(messageReplyMarkup) {
|
||||
if(lastReplyMarkup && lastReplyMarkup.mid >= message.mid) {
|
||||
return false;
|
||||
@ -3049,15 +3055,19 @@ export class AppMessagesManager {
|
||||
|
||||
if(historyStorage.maxOutId &&
|
||||
message.mid < historyStorage.maxOutId &&
|
||||
messageReplyMarkup.pFlags.single_use) {
|
||||
messageReplyMarkup.pFlags.hidden = true;
|
||||
(messageReplyMarkup as ReplyMarkup.replyKeyboardMarkup | ReplyMarkup.replyKeyboardForceReply).pFlags.single_use) {
|
||||
(messageReplyMarkup as ReplyMarkup.replyKeyboardMarkup | ReplyMarkup.replyKeyboardForceReply).pFlags.hidden = true;
|
||||
}
|
||||
messageReplyMarkup = Object.assign({
|
||||
|
||||
messageReplyMarkup.mid = message.mid;
|
||||
/* messageReplyMarkup = Object.assign({
|
||||
mid: message.mid
|
||||
}, messageReplyMarkup);
|
||||
}, messageReplyMarkup); */
|
||||
|
||||
if(messageReplyMarkup._ !== 'replyKeyboardHide') {
|
||||
messageReplyMarkup.fromId = appPeersManager.getPeerId(message.from_id);
|
||||
}
|
||||
|
||||
historyStorage.reply_markup = messageReplyMarkup;
|
||||
// this.log('set', historyStorage.reply_markup)
|
||||
return true;
|
||||
@ -3065,10 +3075,11 @@ export class AppMessagesManager {
|
||||
|
||||
if(message.pFlags.out) {
|
||||
if(lastReplyMarkup) {
|
||||
assumeType<ReplyMarkup.replyKeyboardMarkup>(lastReplyMarkup);
|
||||
if(lastReplyMarkup.pFlags.single_use &&
|
||||
!lastReplyMarkup.pFlags.hidden &&
|
||||
(message.mid > lastReplyMarkup.mid || message.pFlags.is_outgoing) &&
|
||||
message.message) {
|
||||
(message as Message.message).message) {
|
||||
lastReplyMarkup.pFlags.hidden = true;
|
||||
// this.log('set', historyStorage.reply_markup)
|
||||
return true;
|
||||
@ -3079,10 +3090,10 @@ export class AppMessagesManager {
|
||||
}
|
||||
}
|
||||
|
||||
if(message.action &&
|
||||
message.action._ === 'messageActionChatDeleteUser' &&
|
||||
assumeType<Message.messageService>(message);
|
||||
if(message.action?._ === 'messageActionChatDeleteUser' &&
|
||||
(lastReplyMarkup
|
||||
? message.action.user_id === lastReplyMarkup.fromId
|
||||
? message.action.user_id === (lastReplyMarkup as ReplyMarkup.replyKeyboardMarkup).fromId
|
||||
: appUsersManager.isBot(message.action.user_id)
|
||||
)
|
||||
) {
|
||||
@ -4804,7 +4815,7 @@ export class AppMessagesManager {
|
||||
const wasTotalCount = historyStorage.history.length; */
|
||||
|
||||
const mids = messages.map((message) => {
|
||||
if(this.mergeReplyKeyboard(historyStorage, message)) {
|
||||
if(this.mergeReplyKeyboard(historyStorage, message as MyMessage)) {
|
||||
rootScope.dispatchEvent('history_reply_markup', {peerId});
|
||||
}
|
||||
|
||||
|
@ -305,8 +305,9 @@ export default class DialogsStorage {
|
||||
if(!message.pFlags.is_outgoing) {
|
||||
incomingMessage = message;
|
||||
|
||||
if(message.fromId !== dialog.peerId) {
|
||||
this.appStateManager.requestPeer(message.fromId, 'topMessage_' + dialog.peerId, 1);
|
||||
const fromId = message.viaBotId || message.fromId;
|
||||
if(fromId !== dialog.peerId) {
|
||||
this.appStateManager.requestPeer(fromId, 'topMessage_' + dialog.peerId, 1);
|
||||
}
|
||||
|
||||
break;
|
||||
@ -519,14 +520,17 @@ export default class DialogsStorage {
|
||||
const slice = historyStorage.history.slice;
|
||||
/* if(historyStorage === undefined) { // warning
|
||||
historyStorage.history.push(mid);
|
||||
if(this.mergeReplyKeyboard(historyStorage, message)) {
|
||||
rootScope.broadcast('history_reply_markup', {peerId});
|
||||
}
|
||||
} else */if(!slice.length) {
|
||||
historyStorage.history.unshift(mid);
|
||||
if(this.appMessagesManager.mergeReplyKeyboard(historyStorage, message)) {
|
||||
rootScope.dispatchEvent('history_reply_markup', {peerId});
|
||||
}
|
||||
} else if(!slice.isEnd(SliceEnd.Bottom)) { // * this will probably never happen, however, if it does, then it will fix slice with top_message
|
||||
const slice = historyStorage.history.insertSlice([mid]);
|
||||
slice.setEnd(SliceEnd.Bottom);
|
||||
if(this.appMessagesManager.mergeReplyKeyboard(historyStorage, message)) {
|
||||
rootScope.dispatchEvent('history_reply_markup', {peerId});
|
||||
}
|
||||
}
|
||||
|
||||
historyStorage.maxId = mid;
|
||||
|
@ -50,7 +50,8 @@
|
||||
{"name": "random_id", "type": "string"},
|
||||
{"name": "unread", "type": "true"},
|
||||
{"name": "is_outgoing", "type": "true"},
|
||||
{"name": "rReply", "type": "string"}
|
||||
{"name": "rReply", "type": "string"},
|
||||
{"name": "viaBotId", "type": "number"}
|
||||
]
|
||||
}, {
|
||||
"predicate": "messageService",
|
||||
@ -61,7 +62,8 @@
|
||||
{"name": "fromId", "type": "number"},
|
||||
{"name": "unread", "type": "true"},
|
||||
{"name": "is_outgoing", "type": "true"},
|
||||
{"name": "rReply", "type": "string"}
|
||||
{"name": "rReply", "type": "string"},
|
||||
{"name": "viaBotId", "type": "number"}
|
||||
]
|
||||
}, {
|
||||
"predicate": "messageEmpty",
|
||||
@ -237,4 +239,23 @@
|
||||
"params": [
|
||||
{"name": "refreshTime", "type": "number"}
|
||||
]
|
||||
}, {
|
||||
"predicate": "replyKeyboardHide",
|
||||
"params": [
|
||||
{"name": "mid", "type": "number"}
|
||||
]
|
||||
}, {
|
||||
"predicate": "replyKeyboardForceReply",
|
||||
"params": [
|
||||
{"name": "mid", "type": "number"},
|
||||
{"name": "hidden", "type": "true"},
|
||||
{"name": "fromId", "type": "number"}
|
||||
]
|
||||
}, {
|
||||
"predicate": "replyKeyboardMarkup",
|
||||
"params": [
|
||||
{"name": "mid", "type": "number"},
|
||||
{"name": "hidden", "type": "true"},
|
||||
{"name": "fromId", "type": "number"}
|
||||
]
|
||||
}]
|
@ -177,15 +177,8 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
|
||||
.btn-scheduled {
|
||||
position: absolute;
|
||||
right: 3.625rem;
|
||||
align-self: center;
|
||||
display: none !important;
|
||||
margin: 0 !important;
|
||||
|
||||
@include animation-level(2) {
|
||||
animation: grow-icon .4s forwards ease-in-out !important;
|
||||
}
|
||||
/* position: absolute;
|
||||
right: 3.625rem; */
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
@ -199,8 +192,18 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
}
|
||||
|
||||
.float {
|
||||
align-self: center;
|
||||
display: none !important;
|
||||
margin: 0 .75rem 0 0 !important;
|
||||
|
||||
@include animation-level(2) {
|
||||
animation: grow-icon .4s forwards ease-in-out !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.is-recording) {
|
||||
.btn-scheduled.show:not(.hide) {
|
||||
.float.show:not(.hide) {
|
||||
display: flex !important;
|
||||
}
|
||||
}
|
||||
|
@ -1261,6 +1261,12 @@ $bubble-margin: .25rem;
|
||||
}
|
||||
}
|
||||
|
||||
&.with-reply-markup {
|
||||
.bubble-content {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&.with-replies .attachment {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
|
64
src/scss/partials/_replyKeyboard.scss
Normal file
64
src/scss/partials/_replyKeyboard.scss
Normal file
@ -0,0 +1,64 @@
|
||||
.reply-keyboard {
|
||||
background: var(--surface-color);
|
||||
position: absolute !important;
|
||||
right: 0;
|
||||
bottom: calc(100% + .625rem);
|
||||
width: 26.25rem !important;
|
||||
//height: 26.25rem;
|
||||
max-height: 26.25rem;
|
||||
box-shadow: 0px 5px 10px 5px rgba(16, 35, 47, .14);
|
||||
z-index: 3;
|
||||
border-radius: $border-radius-medium;
|
||||
transition: transform var(--esg-transition), opacity var(--esg-transition);
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
transform-origin: bottom right;
|
||||
padding: .625rem !important;
|
||||
display: block !important;
|
||||
|
||||
@include respond-to(esg-bottom-new) {
|
||||
bottom: calc(100% + .5rem);
|
||||
}
|
||||
|
||||
&.active {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
body.animation-level-0 & {
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&-row {
|
||||
display: flex;
|
||||
|
||||
& + & {
|
||||
margin-top: .3125rem;
|
||||
}
|
||||
}
|
||||
|
||||
&-button {
|
||||
width: 100%;
|
||||
border-radius: .375rem;
|
||||
border: 2px solid var(--primary-color);
|
||||
text-align: center;
|
||||
color: var(--primary-color);
|
||||
background-color: transparent;
|
||||
height: 3rem;
|
||||
font-weight: 500;
|
||||
font-size: .9375rem;
|
||||
|
||||
@include animation-level(2) {
|
||||
transition: color .15s, background-color .15s;
|
||||
}
|
||||
|
||||
@include hover() {
|
||||
background-color: var(--primary-color);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
& + & {
|
||||
margin-left: .3125rem;
|
||||
}
|
||||
}
|
||||
}
|
@ -262,6 +262,7 @@ html.night {
|
||||
@import "partials/transition";
|
||||
@import "partials/row";
|
||||
@import "partials/colorPicker";
|
||||
@import "partials/replyKeyboard";
|
||||
|
||||
@import "partials/popups/popup";
|
||||
@import "partials/popups/editAvatar";
|
||||
|
Loading…
x
Reference in New Issue
Block a user