Browse Source

Bot keyboard

Display 'via @username'
master
Eduard Kuzmenko 4 years ago
parent
commit
01ec2005e9
  1. 2
      src/components/chat/bubbleGroups.ts
  2. 21
      src/components/chat/bubbles.ts
  3. 34
      src/components/chat/input.ts
  4. 108
      src/components/chat/replyKeyboard.ts
  5. 243
      src/components/emoticonsDropdown/index.ts
  6. 3
      src/helpers/assumeType.ts
  7. 163
      src/helpers/dropdownHover.ts
  8. 1
      src/lang.ts
  9. 19
      src/layer.d.ts
  10. 47
      src/lib/appManagers/appMessagesManager.ts
  11. 14
      src/lib/storages/dialogs.ts
  12. 25
      src/scripts/in/schema_additional_params.json
  13. 23
      src/scss/partials/_chat.scss
  14. 6
      src/scss/partials/_chatBubble.scss
  15. 64
      src/scss/partials/_replyKeyboard.scss
  16. 1
      src/scss/style.scss

2
src/components/chat/bubbleGroups.ts

@ -43,7 +43,7 @@ export default class BubbleGroups { @@ -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

21
src/components/chat/bubbles.ts

@ -2583,14 +2583,17 @@ export default class ChatBubbles { @@ -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 { @@ -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');
}

34
src/components/chat/input.ts

@ -66,6 +66,7 @@ import AutocompleteHelper from './autocompleteHelper'; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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

@ -0,0 +1,108 @@ @@ -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();
}
}

243
src/components/emoticonsDropdown/index.ts

@ -23,11 +23,10 @@ import AppGifsTab from "../sidebarRight/tabs/gifs"; @@ -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 { @@ -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 { @@ -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
});
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;
this.addEventListener('open', async() => {
if(isTouchSupported) {
//appImManager.chat.input.saveScroll();
if(blurActiveElement()) {
await pause(100);
}
}
clearTimeout(this.displayTimeout);
this.displayTimeout = window.setTimeout(() => {
this.toggle(true);
}, TOGGLE_TIMEOUT);
});
}
}
if(this.element.parentElement !== appImManager.chat.input.chatInput) {
appImManager.chat.input.chatInput.append(this.element);
}
private onMouseOut = (e: MouseEvent) => {
if(KEEP_OPEN) return;
clearTimeout(this.displayTimeout);
if(!this.element.classList.contains('active')) return;
const sel = document.getSelection();
if(sel.rangeCount && document.activeElement === appImManager.chat.input.messageInput) {
this.savedRange = sel.getRangeAt(0);
} else {
this.savedRange = undefined;
}
const toElement = (e as any).toElement as Element;
if(toElement && findUpClassName(toElement, 'emoji-dropdown')) {
return;
}
EmoticonsDropdown.lazyLoadQueue.lock();
//EmoticonsDropdown.lazyLoadQueue.unlock();
animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP);
});
this.displayTimeout = window.setTimeout(() => {
this.toggle(false);
}, TOGGLE_TIMEOUT);
};
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');
private init() {
this.savedRange = undefined;
});
}
protected init() {
this.emojiTab = new EmojiTab();
this.stickersTab = new StickersTab();
this.gifsTab = new GifsTab();
@ -186,17 +177,7 @@ export class EmoticonsDropdown { @@ -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 { @@ -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 { @@ -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 { @@ -445,7 +316,7 @@ export class EmoticonsDropdown {
lazyLoadQueue.intersector.clearVisible();
});
this.events.onOpenAfter.push(() => {
this.addEventListener('opened', () => {
lazyLoadQueue.unlockAndRefresh();
});
}

3
src/helpers/assumeType.ts

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
export default function assumeType<T>(x: unknown): asserts x is T {
return; // ¯\_(ツ)_/¯
}

163
src/helpers/dropdownHover.ts

@ -0,0 +1,163 @@ @@ -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');
}
}

1
src/lang.ts

@ -483,6 +483,7 @@ const lang = { @@ -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

@ -857,7 +857,8 @@ export namespace Message { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 = {

47
src/lib/appManagers/appMessagesManager.ts

@ -17,7 +17,7 @@ import { createPosterForVideo } from "../../helpers/files"; @@ -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"; @@ -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 = { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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});
}

14
src/lib/storages/dialogs.ts

@ -305,8 +305,9 @@ export default class DialogsStorage { @@ -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 { @@ -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;

25
src/scripts/in/schema_additional_params.json

@ -50,7 +50,8 @@ @@ -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 @@ @@ -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 @@ @@ -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"}
]
}]

23
src/scss/partials/_chat.scss

@ -177,15 +177,8 @@ $chat-helper-size: 39px; @@ -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; @@ -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;
}
}

6
src/scss/partials/_chatBubble.scss

@ -1261,6 +1261,12 @@ $bubble-margin: .25rem; @@ -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

@ -0,0 +1,64 @@ @@ -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;
}
}
}

1
src/scss/style.scss

@ -262,6 +262,7 @@ html.night { @@ -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…
Cancel
Save