Autonomous topbar & chat & input
Inner chat Fix pinned change from sticker
This commit is contained in:
parent
d1d5c3074b
commit
3611065bb7
src
components
appMediaViewer.tsappSelectPeers.tsbubbleGroups.tsbuttonMenu.tsbuttonMenuToggle.ts
chat
audio.tsbubbles.tschat.tscontextMenu.tsinput.tsmarkupTooltip.tspinnedContainer.tspinnedMessage.tspinnedMessageBorder.tsreplyContainer.tssearch.tsselection.tsstickersHelper.tstopbar.ts
dialogsContextMenu.tsdivAndCaption.tsemoticonsDropdown
inputField.tsmisc.tspoll.tspopupCreatePoll.tspopupDeleteMessages.tspopupForward.tspopupNewMedia.tspopupStickers.tssidebarLeft/tabs
sidebarRight/tabs
transition.tshelpers
index.hbslib
appManagers
appChatsManager.tsappDialogsManager.tsappDocsManager.tsappImManager.tsappMessagesManager.tsappPollsManager.tsappWebPagesManager.ts
logger.tslottieLoader.tsmtproto
rootScope.tsscss
@ -1324,7 +1324,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
|
||||
else fromRight = this.currentMessageID > mid ? 1 : -1;
|
||||
} else {
|
||||
this.reverse = reverse;
|
||||
this.peerID = rootScope.selectedPeerID;
|
||||
this.peerID = message.peerID;
|
||||
}
|
||||
|
||||
this.currentMessageID = mid;
|
||||
|
@ -90,7 +90,7 @@ export default class AppSelectPeers {
|
||||
this.container.append(topContainer, delimiter);
|
||||
}
|
||||
|
||||
this.chatsContainer.classList.add('chats-container');
|
||||
this.chatsContainer.classList.add('chatlist-container');
|
||||
this.chatsContainer.append(this.list);
|
||||
this.scrollable = new Scrollable(this.chatsContainer);
|
||||
this.scrollable.setVirtualContainer(this.list);
|
||||
|
@ -25,7 +25,7 @@ export default class BubbleGroups {
|
||||
let group: HTMLDivElement[];
|
||||
|
||||
// fix for saved messages forward to self
|
||||
if(fromID == rootScope.myID && rootScope.selectedPeerID == rootScope.myID && message.fwdFromID == fromID) {
|
||||
if(fromID == rootScope.myID && message.peerID == rootScope.myID && message.fwdFromID == fromID) {
|
||||
fromID = -fromID;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,16 @@
|
||||
import { attachClickEvent, cancelEvent, CLICK_EVENT_NAME } from "../helpers/dom";
|
||||
import { attachClickEvent, AttachClickOptions, cancelEvent, CLICK_EVENT_NAME } from "../helpers/dom";
|
||||
import ListenerSetter from "../helpers/listenerSetter";
|
||||
import { closeBtnMenu } from "./misc";
|
||||
import { ripple } from "./ripple";
|
||||
|
||||
export type ButtonMenuItemOptions = {icon: string, text: string, onClick: (e: MouseEvent | TouchEvent) => void, element?: HTMLElement/* , cancelEvent?: true */};
|
||||
export type ButtonMenuItemOptions = {
|
||||
icon: string,
|
||||
text: string,
|
||||
onClick: (e: MouseEvent | TouchEvent) => void,
|
||||
element?: HTMLElement,
|
||||
options?: AttachClickOptions
|
||||
/* , cancelEvent?: true */
|
||||
};
|
||||
|
||||
const ButtonMenuItem = (options: ButtonMenuItemOptions) => {
|
||||
if(options.element) return options.element;
|
||||
@ -19,15 +27,25 @@ const ButtonMenuItem = (options: ButtonMenuItemOptions) => {
|
||||
cancelEvent(e);
|
||||
onClick(e);
|
||||
closeBtnMenu();
|
||||
} : onClick);
|
||||
} : onClick, options.options);
|
||||
|
||||
return options.element = el;
|
||||
};
|
||||
|
||||
const ButtonMenu = (buttons: ButtonMenuItemOptions[]) => {
|
||||
const ButtonMenu = (buttons: ButtonMenuItemOptions[], listenerSetter?: ListenerSetter) => {
|
||||
const el = document.createElement('div');
|
||||
el.classList.add('btn-menu');
|
||||
|
||||
if(listenerSetter) {
|
||||
buttons.forEach(b => {
|
||||
if(b.options) {
|
||||
b.options.listenerSetter = listenerSetter;
|
||||
} else {
|
||||
b.options = {listenerSetter};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const items = buttons.map(ButtonMenuItem);
|
||||
|
||||
el.append(...items);
|
||||
|
@ -1,19 +1,23 @@
|
||||
import { cancelEvent, CLICK_EVENT_NAME } from "../helpers/dom";
|
||||
import { AttachClickOptions, cancelEvent, CLICK_EVENT_NAME } from "../helpers/dom";
|
||||
import ListenerSetter from "../helpers/listenerSetter";
|
||||
import ButtonIcon from "./buttonIcon";
|
||||
import ButtonMenu, { ButtonMenuItemOptions } from "./buttonMenu";
|
||||
import { closeBtnMenu, openBtnMenu } from "./misc";
|
||||
|
||||
const ButtonMenuToggle = (options: Partial<{noRipple: true, onlyMobile: true}> = {}, direction: 'bottom-left', buttons: ButtonMenuItemOptions[]) => {
|
||||
const ButtonMenuToggle = (options: Partial<{noRipple: true, onlyMobile: true, listenerSetter: ListenerSetter}> = {}, direction: 'bottom-left' | 'top-left', buttons: ButtonMenuItemOptions[], onOpen?: () => void) => {
|
||||
const button = ButtonIcon('more btn-menu-toggle', options);
|
||||
const btnMenu = ButtonMenu(buttons);
|
||||
|
||||
const btnMenu = ButtonMenu(buttons, options.listenerSetter);
|
||||
btnMenu.classList.add(direction);
|
||||
ButtonMenuToggleHandler(button);
|
||||
ButtonMenuToggleHandler(button, onOpen, options);
|
||||
button.append(btnMenu);
|
||||
return button;
|
||||
};
|
||||
|
||||
const ButtonMenuToggleHandler = (el: HTMLElement) => {
|
||||
(el as HTMLElement).addEventListener(CLICK_EVENT_NAME, (e) => {
|
||||
const ButtonMenuToggleHandler = (el: HTMLElement, onOpen?: () => void, options?: AttachClickOptions) => {
|
||||
const add = options?.listenerSetter ? options.listenerSetter.add.bind(options.listenerSetter, el) : el.addEventListener.bind(el);
|
||||
|
||||
add(CLICK_EVENT_NAME, (e: Event) => {
|
||||
//console.log('click pageIm');
|
||||
if(!el.classList.contains('btn-menu-toggle')) return false;
|
||||
|
||||
@ -24,6 +28,7 @@ const ButtonMenuToggleHandler = (el: HTMLElement) => {
|
||||
if(el.classList.contains('menu-open')) {
|
||||
closeBtnMenu();
|
||||
} else {
|
||||
onOpen && onOpen();
|
||||
openBtnMenu(openedMenu);
|
||||
}
|
||||
});
|
||||
|
@ -1,5 +1,6 @@
|
||||
import appMessagesManager from "../../lib/appManagers/appMessagesManager";
|
||||
import appPeersManager from "../../lib/appManagers/appPeersManager";
|
||||
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
|
||||
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
|
||||
import type ChatTopbar from "./topbar";
|
||||
import { RichTextProcessor } from "../../lib/richtextprocessor";
|
||||
import rootScope from "../../lib/rootScope";
|
||||
import { cancelEvent } from "../../helpers/dom";
|
||||
@ -7,12 +8,13 @@ import appMediaPlaybackController from "../appMediaPlaybackController";
|
||||
import DivAndCaption from "../divAndCaption";
|
||||
import { formatDate } from "../wrappers";
|
||||
import PinnedContainer from "./pinnedContainer";
|
||||
import Chat from "./chat";
|
||||
|
||||
export class ChatAudio extends PinnedContainer {
|
||||
export default class ChatAudio extends PinnedContainer {
|
||||
private toggleEl: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
super('audio', new DivAndCaption('pinned-audio', (title: string, subtitle: string) => {
|
||||
constructor(protected topbar: ChatTopbar, protected chat: Chat, protected appMessagesManager: AppMessagesManager, protected appPeersManager: AppPeersManager) {
|
||||
super(topbar, chat, 'audio', new DivAndCaption('pinned-audio', (title: string, subtitle: string) => {
|
||||
this.divAndCaption.title.innerHTML = title;
|
||||
this.divAndCaption.subtitle.innerHTML = subtitle;
|
||||
}), () => {
|
||||
@ -25,14 +27,14 @@ export class ChatAudio extends PinnedContainer {
|
||||
|
||||
this.toggleEl = document.createElement('button');
|
||||
this.toggleEl.classList.add('pinned-audio-ico', 'tgico', 'btn-icon');
|
||||
this.toggleEl.addEventListener('click', (e) => {
|
||||
this.topbar.listenerSetter.add(this.toggleEl, 'click', (e) => {
|
||||
cancelEvent(e);
|
||||
appMediaPlaybackController.toggle();
|
||||
});
|
||||
|
||||
this.wrapper.prepend(this.toggleEl);
|
||||
|
||||
rootScope.on('audio_play', (e) => {
|
||||
this.topbar.listenerSetter.add(rootScope, 'audio_play', (e) => {
|
||||
const {doc, mid} = e.detail;
|
||||
|
||||
let title: string, subtitle: string;
|
||||
@ -51,7 +53,7 @@ export class ChatAudio extends PinnedContainer {
|
||||
this.toggle(false);
|
||||
});
|
||||
|
||||
rootScope.on('audio_pause', () => {
|
||||
this.topbar.listenerSetter.add(rootScope, 'audio_pause', () => {
|
||||
this.toggleEl.classList.remove('flip-icon');
|
||||
});
|
||||
}
|
||||
|
2302
src/components/chat/bubbles.ts
Normal file
2302
src/components/chat/bubbles.ts
Normal file
File diff suppressed because it is too large
Load Diff
172
src/components/chat/chat.ts
Normal file
172
src/components/chat/chat.ts
Normal file
@ -0,0 +1,172 @@
|
||||
import type { AppChatsManager } from "../../lib/appManagers/appChatsManager";
|
||||
import type { AppDocsManager } from "../../lib/appManagers/appDocsManager";
|
||||
import type { AppImManager } from "../../lib/appManagers/appImManager";
|
||||
import type { AppInlineBotsManager } from "../../lib/appManagers/AppInlineBotsManager";
|
||||
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
|
||||
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
|
||||
import type { AppPhotosManager } from "../../lib/appManagers/appPhotosManager";
|
||||
import type { AppPollsManager } from "../../lib/appManagers/appPollsManager";
|
||||
import type { AppProfileManager } from "../../lib/appManagers/appProfileManager";
|
||||
import type { AppStickersManager } from "../../lib/appManagers/appStickersManager";
|
||||
import type { AppUsersManager } from "../../lib/appManagers/appUsersManager";
|
||||
import type { AppWebPagesManager } from "../../lib/appManagers/appWebPagesManager";
|
||||
import { logger, LogLevels } from "../../lib/logger";
|
||||
import rootScope from "../../lib/rootScope";
|
||||
import appSidebarRight, { AppSidebarRight } from "../sidebarRight";
|
||||
import ChatBubbles from "./bubbles";
|
||||
import ChatContextMenu from "./contextMenu";
|
||||
import ChatInput from "./input";
|
||||
import ChatSelection from "./selection";
|
||||
import ChatTopbar from "./topbar";
|
||||
|
||||
export default class Chat {
|
||||
public container: HTMLElement;
|
||||
public backgroundEl: HTMLElement;
|
||||
|
||||
public topbar: ChatTopbar;
|
||||
public bubbles: ChatBubbles;
|
||||
public input: ChatInput;
|
||||
public selection: ChatSelection;
|
||||
public contextMenu: ChatContextMenu;
|
||||
|
||||
public peerID = 0;
|
||||
public setPeerPromise: Promise<void>;
|
||||
public peerChanged: boolean;
|
||||
|
||||
public log: ReturnType<typeof logger>;
|
||||
|
||||
constructor(public appImManager: AppImManager, private appChatsManager: AppChatsManager, private appDocsManager: AppDocsManager, private appInlineBotsManager: AppInlineBotsManager, private appMessagesManager: AppMessagesManager, private appPeersManager: AppPeersManager, private appPhotosManager: AppPhotosManager, private appProfileManager: AppProfileManager, private appStickersManager: AppStickersManager, private appUsersManager: AppUsersManager, private appWebPagesManager: AppWebPagesManager, private appSidebarRight: AppSidebarRight, private appPollsManager: AppPollsManager) {
|
||||
this.container = document.createElement('div');
|
||||
this.container.classList.add('chat');
|
||||
|
||||
this.backgroundEl = document.createElement('div');
|
||||
this.backgroundEl.classList.add('chat-background');
|
||||
|
||||
// * constructor end
|
||||
|
||||
this.log = logger('CHAT', LogLevels.log | LogLevels.warn | LogLevels.debug | LogLevels.error);
|
||||
this.log.error('Chat construction');
|
||||
|
||||
this.container.append(this.backgroundEl);
|
||||
this.appImManager.chatsContainer.append(this.container);
|
||||
}
|
||||
|
||||
private init() {
|
||||
this.topbar = new ChatTopbar(this, appSidebarRight, this.appMessagesManager, this.appPeersManager, this.appChatsManager, this.appUsersManager, this.appProfileManager);
|
||||
this.bubbles = new ChatBubbles(this, this.appMessagesManager, this.appSidebarRight, this.appStickersManager, this.appUsersManager, this.appInlineBotsManager, this.appPhotosManager, this.appDocsManager, this.appPeersManager, this.appChatsManager);
|
||||
this.input = new ChatInput(this, this.appMessagesManager, this.appDocsManager, this.appChatsManager, this.appPeersManager, this.appWebPagesManager, this.appImManager);
|
||||
this.selection = new ChatSelection(this.bubbles, this.input, this.appMessagesManager);
|
||||
this.contextMenu = new ChatContextMenu(this.bubbles.bubblesContainer, this, this.appMessagesManager, this.appChatsManager, this.appPeersManager, this.appPollsManager);
|
||||
|
||||
this.container.append(this.topbar.container, this.bubbles.bubblesContainer, this.input.chatInput);
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
const perf = performance.now();
|
||||
|
||||
this.topbar.destroy();
|
||||
this.bubbles.destroy();
|
||||
this.input.destroy();
|
||||
|
||||
delete this.topbar;
|
||||
delete this.bubbles;
|
||||
delete this.input;
|
||||
delete this.selection;
|
||||
delete this.contextMenu;
|
||||
|
||||
this.container.remove();
|
||||
|
||||
this.log.error('Chat destroy time:', performance.now() - perf);
|
||||
}
|
||||
|
||||
public cleanup() {
|
||||
this.input.cleanup();
|
||||
this.selection.cleanup();
|
||||
|
||||
this.peerChanged = false;
|
||||
}
|
||||
|
||||
public setPeer(peerID: number, lastMsgID?: number) {
|
||||
if(this.init) {
|
||||
this.init();
|
||||
this.init = null;
|
||||
}
|
||||
|
||||
//console.time('appImManager setPeer');
|
||||
//console.time('appImManager setPeer pre promise');
|
||||
////console.time('appImManager: pre render start');
|
||||
if(peerID == 0) {
|
||||
appSidebarRight.toggleSidebar(false);
|
||||
this.peerID = peerID;
|
||||
this.cleanup();
|
||||
this.topbar.setPeer(peerID);
|
||||
this.bubbles.setPeer(peerID);
|
||||
rootScope.broadcast('peer_changed', peerID);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const samePeer = this.peerID == peerID;
|
||||
|
||||
// set new
|
||||
if(!samePeer) {
|
||||
if(appSidebarRight.historyTabIDs[appSidebarRight.historyTabIDs.length - 1] == AppSidebarRight.SLIDERITEMSIDS.search) {
|
||||
appSidebarRight.searchTab.closeBtn?.click();
|
||||
}
|
||||
|
||||
this.peerID = peerID;
|
||||
appSidebarRight.sharedMediaTab.setPeer(peerID);
|
||||
this.cleanup();
|
||||
} else {
|
||||
this.peerChanged = true;
|
||||
}
|
||||
|
||||
const result = this.bubbles.setPeer(peerID, lastMsgID);
|
||||
if(!result) {
|
||||
return;
|
||||
}
|
||||
|
||||
const {cached, promise} = result;
|
||||
|
||||
// clear
|
||||
if(!cached) {
|
||||
if(!samePeer) {
|
||||
this.finishPeerChange();
|
||||
}
|
||||
}
|
||||
|
||||
//console.timeEnd('appImManager setPeer pre promise');
|
||||
|
||||
this.setPeerPromise = promise.then(() => {
|
||||
if(cached) {
|
||||
if(!samePeer) {
|
||||
this.finishPeerChange();
|
||||
}
|
||||
}
|
||||
}).finally(() => {
|
||||
if(this.peerID == peerID) {
|
||||
this.setPeerPromise = null;
|
||||
}
|
||||
});
|
||||
|
||||
appSidebarRight.sharedMediaTab.setLoadMutex(this.setPeerPromise);
|
||||
appSidebarRight.sharedMediaTab.loadSidebarMedia(true);
|
||||
|
||||
return this.setPeerPromise;
|
||||
}
|
||||
|
||||
public finishPeerChange() {
|
||||
if(this.peerChanged) return;
|
||||
|
||||
let peerID = this.peerID;
|
||||
this.peerChanged = true;
|
||||
|
||||
this.topbar.setPeer(peerID);
|
||||
this.bubbles.finishPeerChange();
|
||||
this.input.finishPeerChange();
|
||||
|
||||
appSidebarRight.sharedMediaTab.fillProfileElements();
|
||||
|
||||
rootScope.broadcast('peer_changed', this.peerID);
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
|
||||
import type { AppChatsManager } from "../../lib/appManagers/appChatsManager";
|
||||
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
|
||||
import type { AppPollsManager, Poll } from "../../lib/appManagers/appPollsManager";
|
||||
import type Chat from "./chat";
|
||||
import { isTouchSupported } from "../../helpers/touchSupport";
|
||||
import appChatsManager from "../../lib/appManagers/appChatsManager";
|
||||
import appImManager from "../../lib/appManagers/appImManager";
|
||||
import appMessagesManager from "../../lib/appManagers/appMessagesManager";
|
||||
import appPeersManager from "../../lib/appManagers/appPeersManager";
|
||||
import appPollsManager, { Poll } from "../../lib/appManagers/appPollsManager";
|
||||
import rootScope from "../../lib/rootScope";
|
||||
import { attachClickEvent, cancelEvent, cancelSelection, findUpClassName } from "../../helpers/dom";
|
||||
import ButtonMenu, { ButtonMenuItemOptions } from "../buttonMenu";
|
||||
@ -22,7 +22,7 @@ export default class ChatContextMenu {
|
||||
public peerID: number;
|
||||
public msgID: number;
|
||||
|
||||
constructor(private attachTo: HTMLElement) {
|
||||
constructor(private attachTo: HTMLElement, private chat: Chat, private appMessagesManager: AppMessagesManager, private appChatsManager: AppChatsManager, private appPeersManager: AppPeersManager, private appPollsManager: AppPollsManager) {
|
||||
const onContextMenu = (e: MouseEvent | Touch) => {
|
||||
if(this.init) {
|
||||
this.init();
|
||||
@ -49,17 +49,17 @@ export default class ChatContextMenu {
|
||||
if(!mid) return;
|
||||
|
||||
// * если открыть контекстное меню для альбома не по бабблу, и последний элемент не выбран, чтобы показать остальные пункты
|
||||
if(appImManager.chatSelection.isSelecting && !bubbleContainer) {
|
||||
if(chat.selection.isSelecting && !bubbleContainer) {
|
||||
const mids = appMessagesManager.getMidsByMid(mid);
|
||||
if(mids.length > 1) {
|
||||
const selectedMid = appImManager.chatSelection.selectedMids.has(mid) ? mid : mids.find(mid => appImManager.chatSelection.selectedMids.has(mid));
|
||||
const selectedMid = chat.selection.selectedMids.has(mid) ? mid : mids.find(mid => chat.selection.selectedMids.has(mid));
|
||||
if(selectedMid) {
|
||||
mid = selectedMid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.peerID = rootScope.selectedPeerID;
|
||||
this.peerID = this.chat.peerID;
|
||||
//this.msgID = msgID;
|
||||
this.target = e.target as HTMLElement;
|
||||
|
||||
@ -75,7 +75,7 @@ export default class ChatContextMenu {
|
||||
let good: boolean;
|
||||
|
||||
//if((appImManager.chatSelection.isSelecting && !button.withSelection) || (button.withSelection && !appImManager.chatSelection.isSelecting)) {
|
||||
if(appImManager.chatSelection.isSelecting && !button.withSelection) {
|
||||
if(chat.selection.isSelecting && !button.withSelection) {
|
||||
good = false;
|
||||
} else {
|
||||
good = bubbleContainer || isTouchSupported ?
|
||||
@ -98,24 +98,24 @@ export default class ChatContextMenu {
|
||||
|
||||
if(isTouchSupported) {
|
||||
attachClickEvent(attachTo, (e) => {
|
||||
if(appImManager.chatSelection.isSelecting) {
|
||||
if(chat.selection.isSelecting) {
|
||||
return;
|
||||
}
|
||||
|
||||
const className = (e.target as HTMLElement).className;
|
||||
if(!className || !className.includes) return;
|
||||
|
||||
appImManager.log('touchend', e);
|
||||
chat.log('touchend', e);
|
||||
|
||||
const good = ['bubble', 'bubble__container', 'message', 'time', 'inner'].find(c => className.match(new RegExp(c + '($|\\s)')));
|
||||
if(good) {
|
||||
cancelEvent(e);
|
||||
onContextMenu((e as TouchEvent).changedTouches[0]);
|
||||
}
|
||||
});
|
||||
}, {listenerSetter: this.chat.bubbles.listenerSetter});
|
||||
|
||||
attachContextMenuListener(attachTo, (e) => {
|
||||
if(appImManager.chatSelection.isSelecting) return;
|
||||
if(chat.selection.isSelecting) return;
|
||||
|
||||
// * these two lines will fix instant text selection on iOS Safari
|
||||
attachTo.classList.add('no-select');
|
||||
@ -127,34 +127,34 @@ export default class ChatContextMenu {
|
||||
//cancelEvent(e as any);
|
||||
const bubble = findUpClassName(e.target, 'album-item') || findUpClassName(e.target, 'bubble');
|
||||
if(bubble) {
|
||||
appImManager.chatSelection.toggleByBubble(bubble);
|
||||
chat.selection.toggleByBubble(bubble);
|
||||
}
|
||||
});
|
||||
} else attachContextMenuListener(attachTo, onContextMenu);
|
||||
}, this.chat.bubbles.listenerSetter);
|
||||
} else attachContextMenuListener(attachTo, onContextMenu, this.chat.bubbles.listenerSetter);
|
||||
}
|
||||
|
||||
private init = () => {
|
||||
private init() {
|
||||
this.buttons = [{
|
||||
icon: 'reply',
|
||||
text: 'Reply',
|
||||
onClick: this.onReplyClick,
|
||||
verify: () => (this.peerID > 0 || appChatsManager.hasRights(-this.peerID, 'send')) && this.msgID > 0/* ,
|
||||
verify: () => (this.peerID > 0 || this.appChatsManager.hasRights(-this.peerID, 'send')) && this.msgID > 0/* ,
|
||||
cancelEvent: true */
|
||||
}, {
|
||||
icon: 'edit',
|
||||
text: 'Edit',
|
||||
onClick: this.onEditClick,
|
||||
verify: () => appMessagesManager.canEditMessage(this.msgID, 'text')
|
||||
verify: () => this.appMessagesManager.canEditMessage(this.msgID, 'text')
|
||||
}, {
|
||||
icon: 'copy',
|
||||
text: 'Copy',
|
||||
onClick: this.onCopyClick,
|
||||
verify: () => !!appMessagesManager.getMessage(this.msgID).message
|
||||
verify: () => !!this.appMessagesManager.getMessage(this.msgID).message
|
||||
}, {
|
||||
icon: 'copy',
|
||||
text: 'Copy selected',
|
||||
onClick: this.onCopyClick,
|
||||
verify: () => appImManager.chatSelection.selectedMids.has(this.msgID) && !![...appImManager.chatSelection.selectedMids].find(mid => !!appMessagesManager.getMessage(mid).message),
|
||||
verify: () => this.chat.selection.selectedMids.has(this.msgID) && !![...this.chat.selection.selectedMids].find(mid => !!this.appMessagesManager.getMessage(mid).message),
|
||||
notDirect: () => true,
|
||||
withSelection: true
|
||||
}, {
|
||||
@ -162,22 +162,22 @@ export default class ChatContextMenu {
|
||||
text: 'Pin',
|
||||
onClick: this.onPinClick,
|
||||
verify: () => {
|
||||
const message = appMessagesManager.getMessage(this.msgID);
|
||||
const message = this.appMessagesManager.getMessage(this.msgID);
|
||||
// for new layer
|
||||
// return this.msgID > 0 && message._ != 'messageService' && appImManager.pinnedMsgID != this.msgID && (this.peerID > 0 || appChatsManager.hasRights(-this.peerID, 'pin'));
|
||||
return this.msgID > 0 && message._ != 'messageService' && /* appImManager.pinnedMsgID != this.msgID && */ (this.peerID == rootScope.myID || (this.peerID < 0 && appChatsManager.hasRights(-this.peerID, 'pin')));
|
||||
return this.msgID > 0 && message._ != 'messageService' && /* appImManager.pinnedMsgID != this.msgID && */ (this.peerID == rootScope.myID || (this.peerID < 0 && this.appChatsManager.hasRights(-this.peerID, 'pin')));
|
||||
}
|
||||
}, {
|
||||
icon: 'unpin',
|
||||
text: 'Unpin',
|
||||
onClick: this.onUnpinClick,
|
||||
verify: () => /* appImManager.pinnedMsgID == this.msgID && */ appPeersManager.canPinMessage(this.peerID)
|
||||
verify: () => /* appImManager.pinnedMsgID == this.msgID && */ this.appPeersManager.canPinMessage(this.peerID)
|
||||
}, {
|
||||
icon: 'revote',
|
||||
text: 'Revote',
|
||||
onClick: this.onRetractVote,
|
||||
verify: () => {
|
||||
const message = appMessagesManager.getMessage(this.msgID);
|
||||
const message = this.appMessagesManager.getMessage(this.msgID);
|
||||
const poll = message.media?.poll as Poll;
|
||||
return poll && poll.chosenIndexes.length && !poll.pFlags.closed && !poll.pFlags.quiz;
|
||||
}/* ,
|
||||
@ -187,9 +187,9 @@ export default class ChatContextMenu {
|
||||
text: 'Stop poll',
|
||||
onClick: this.onStopPoll,
|
||||
verify: () => {
|
||||
const message = appMessagesManager.getMessage(this.msgID);
|
||||
const message = this.appMessagesManager.getMessage(this.msgID);
|
||||
const poll = message.media?.poll;
|
||||
return appMessagesManager.canEditMessage(this.msgID, 'poll') && poll && !poll.pFlags.closed && this.msgID > 0;
|
||||
return this.appMessagesManager.canEditMessage(this.msgID, 'poll') && poll && !poll.pFlags.closed && this.msgID > 0;
|
||||
}/* ,
|
||||
cancelEvent: true */
|
||||
}, {
|
||||
@ -201,7 +201,7 @@ export default class ChatContextMenu {
|
||||
icon: 'forward',
|
||||
text: 'Forward selected',
|
||||
onClick: this.onForwardClick,
|
||||
verify: () => appImManager.chatSelection.selectedMids.has(this.msgID) && !appImManager.chatSelection.selectionForwardBtn.hasAttribute('disabled'),
|
||||
verify: () => this.chat.selection.selectedMids.has(this.msgID) && !this.chat.selection.selectionForwardBtn.hasAttribute('disabled'),
|
||||
notDirect: () => true,
|
||||
withSelection: true
|
||||
}, {
|
||||
@ -209,8 +209,8 @@ export default class ChatContextMenu {
|
||||
text: 'Select',
|
||||
onClick: this.onSelectClick,
|
||||
verify: () => {
|
||||
const message = appMessagesManager.getMessage(this.msgID);
|
||||
return !message.action && !appImManager.chatSelection.selectedMids.has(this.msgID);
|
||||
const message = this.appMessagesManager.getMessage(this.msgID);
|
||||
return !message.action && !this.chat.selection.selectedMids.has(this.msgID);
|
||||
},
|
||||
notDirect: () => true,
|
||||
withSelection: true
|
||||
@ -218,46 +218,46 @@ export default class ChatContextMenu {
|
||||
icon: 'select',
|
||||
text: 'Clear selection',
|
||||
onClick: this.onClearSelectionClick,
|
||||
verify: () => appImManager.chatSelection.selectedMids.has(this.msgID),
|
||||
verify: () => this.chat.selection.selectedMids.has(this.msgID),
|
||||
notDirect: () => true,
|
||||
withSelection: true
|
||||
}, {
|
||||
icon: 'delete danger',
|
||||
text: 'Delete',
|
||||
onClick: this.onDeleteClick,
|
||||
verify: () => appMessagesManager.canDeleteMessage(this.msgID)
|
||||
verify: () => this.appMessagesManager.canDeleteMessage(this.msgID)
|
||||
}, {
|
||||
icon: 'delete danger',
|
||||
text: 'Delete selected',
|
||||
onClick: this.onDeleteClick,
|
||||
verify: () => appImManager.chatSelection.selectedMids.has(this.msgID) && !appImManager.chatSelection.selectionDeleteBtn.hasAttribute('disabled'),
|
||||
verify: () => this.chat.selection.selectedMids.has(this.msgID) && !this.chat.selection.selectionDeleteBtn.hasAttribute('disabled'),
|
||||
notDirect: () => true,
|
||||
withSelection: true
|
||||
}];
|
||||
|
||||
this.element = ButtonMenu(this.buttons);
|
||||
this.element = ButtonMenu(this.buttons, this.chat.bubbles.listenerSetter);
|
||||
this.element.id = 'bubble-contextmenu';
|
||||
appImManager.chatInput.parentElement.insertBefore(this.element, appImManager.chatInput);
|
||||
this.chat.container.append(this.element);
|
||||
};
|
||||
|
||||
private onReplyClick = () => {
|
||||
const message = appMessagesManager.getMessage(this.msgID);
|
||||
const chatInputC = appImManager.chatInputC;
|
||||
const message = this.appMessagesManager.getMessage(this.msgID);
|
||||
const chatInputC = this.chat.input;
|
||||
const f = () => {
|
||||
chatInputC.setTopInfo('reply', f, appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message);
|
||||
chatInputC.setTopInfo('reply', f, this.appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message);
|
||||
chatInputC.replyToMsgID = this.msgID;
|
||||
};
|
||||
f();
|
||||
};
|
||||
|
||||
private onEditClick = () => {
|
||||
appImManager.chatInputC.initMessageEditing(this.msgID);
|
||||
this.chat.input.initMessageEditing(this.msgID);
|
||||
};
|
||||
|
||||
private onCopyClick = () => {
|
||||
const mids = appImManager.chatSelection.isSelecting ? [...appImManager.chatSelection.selectedMids] : [this.msgID];
|
||||
const mids = this.chat.selection.isSelecting ? [...this.chat.selection.selectedMids] : [this.msgID];
|
||||
const str = mids.reduce((acc, mid) => {
|
||||
const message = appMessagesManager.getMessage(mid);
|
||||
const message = this.appMessagesManager.getMessage(mid);
|
||||
return acc + (message?.message ? message.message + '\n' : '');
|
||||
}, '').trim();
|
||||
|
||||
@ -265,42 +265,42 @@ export default class ChatContextMenu {
|
||||
};
|
||||
|
||||
private onPinClick = () => {
|
||||
new PopupPinMessage(rootScope.selectedPeerID, this.msgID);
|
||||
new PopupPinMessage(this.peerID, this.msgID);
|
||||
};
|
||||
|
||||
private onUnpinClick = () => {
|
||||
new PopupPinMessage(rootScope.selectedPeerID, this.msgID, true);
|
||||
new PopupPinMessage(this.peerID, this.msgID, true);
|
||||
};
|
||||
|
||||
private onRetractVote = () => {
|
||||
appPollsManager.sendVote(this.msgID, []);
|
||||
this.appPollsManager.sendVote(this.msgID, []);
|
||||
};
|
||||
|
||||
private onStopPoll = () => {
|
||||
appPollsManager.stopPoll(this.msgID);
|
||||
this.appPollsManager.stopPoll(this.msgID);
|
||||
};
|
||||
|
||||
private onForwardClick = () => {
|
||||
if(appImManager.chatSelection.isSelecting) {
|
||||
appImManager.chatSelection.selectionForwardBtn.click();
|
||||
if(this.chat.selection.isSelecting) {
|
||||
this.chat.selection.selectionForwardBtn.click();
|
||||
} else {
|
||||
new PopupForward(this.isTargetAnAlbumItem ? [this.msgID] : appMessagesManager.getMidsByMid(this.msgID));
|
||||
new PopupForward(this.isTargetAnAlbumItem ? [this.msgID] : this.appMessagesManager.getMidsByMid(this.msgID));
|
||||
}
|
||||
};
|
||||
|
||||
private onSelectClick = () => {
|
||||
appImManager.chatSelection.toggleByBubble(findUpClassName(this.target, 'album-item') || findUpClassName(this.target, 'bubble'));
|
||||
this.chat.selection.toggleByBubble(findUpClassName(this.target, 'album-item') || findUpClassName(this.target, 'bubble'));
|
||||
};
|
||||
|
||||
private onClearSelectionClick = () => {
|
||||
appImManager.chatSelection.cancelSelection();
|
||||
this.chat.selection.cancelSelection();
|
||||
};
|
||||
|
||||
private onDeleteClick = () => {
|
||||
if(appImManager.chatSelection.isSelecting) {
|
||||
appImManager.chatSelection.selectionDeleteBtn.click();
|
||||
if(this.chat.selection.isSelecting) {
|
||||
this.chat.selection.selectionDeleteBtn.click();
|
||||
} else {
|
||||
new PopupDeleteMessages(this.isTargetAnAlbumItem ? [this.msgID] : appMessagesManager.getMidsByMid(this.msgID));
|
||||
new PopupDeleteMessages(this.isTargetAnAlbumItem ? [this.msgID] : this.appMessagesManager.getMidsByMid(this.msgID));
|
||||
}
|
||||
};
|
||||
}
|
@ -1,381 +1,65 @@
|
||||
import type { AppChatsManager } from '../../lib/appManagers/appChatsManager';
|
||||
import type { AppDocsManager } from "../../lib/appManagers/appDocsManager";
|
||||
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
|
||||
import type { AppPeersManager } from '../../lib/appManagers/appPeersManager';
|
||||
import type { AppWebPagesManager } from "../../lib/appManagers/appWebPagesManager";
|
||||
import type { AppImManager } from '../../lib/appManagers/appImManager';
|
||||
import type Chat from './chat';
|
||||
import Recorder from '../../../public/recorder.min';
|
||||
import { isTouchSupported } from "../../helpers/touchSupport";
|
||||
import appChatsManager from '../../lib/appManagers/appChatsManager';
|
||||
import appDocsManager, { MyDocument } from "../../lib/appManagers/appDocsManager";
|
||||
import appImManager, { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
|
||||
import appMessagesManager from "../../lib/appManagers/appMessagesManager";
|
||||
import appPeersManager from '../../lib/appManagers/appPeersManager';
|
||||
import appWebPagesManager from "../../lib/appManagers/appWebPagesManager";
|
||||
import apiManager from "../../lib/mtproto/mtprotoworker";
|
||||
//import Recorder from '../opus-recorder/dist/recorder.min';
|
||||
import opusDecodeController from "../../lib/opusDecodeController";
|
||||
import { RichTextProcessor } from "../../lib/richtextprocessor";
|
||||
import rootScope from '../../lib/rootScope';
|
||||
import { blurActiveElement, cancelEvent, CLICK_EVENT_NAME, findUpClassName, getRichValue, getSelectedNodes, isInputEmpty, markdownTags, MarkdownType, placeCaretAtEnd, serializeNodes } from "../../helpers/dom";
|
||||
import ButtonMenu, { ButtonMenuItemOptions } from '../buttonMenu';
|
||||
import emoticonsDropdown, { EmoticonsDropdown } from "../emoticonsDropdown";
|
||||
import RichTextProcessor from "../../lib/richtextprocessor";
|
||||
import { blurActiveElement, cancelEvent, cancelSelection, CLICK_EVENT_NAME, findUpClassName, getRichValue, getSelectedNodes, isInputEmpty, markdownTags, MarkdownType, placeCaretAtEnd, serializeNodes } from "../../helpers/dom";
|
||||
import { ButtonMenuItemOptions } from '../buttonMenu';
|
||||
import emoticonsDropdown from "../emoticonsDropdown";
|
||||
import PopupCreatePoll from "../popupCreatePoll";
|
||||
import PopupForward from '../popupForward';
|
||||
import PopupNewMedia from '../popupNewMedia';
|
||||
import { ripple } from '../ripple';
|
||||
import Scrollable from "../scrollable";
|
||||
import { toast } from "../toast";
|
||||
import { wrapReply } from "../wrappers";
|
||||
import InputField from '../inputField';
|
||||
import { MessageEntity } from '../../layer';
|
||||
import MarkupTooltip from './markupTooltip';
|
||||
import StickersHelper from './stickersHelper';
|
||||
import ButtonIcon from '../buttonIcon';
|
||||
import appStickersManager from '../../lib/appManagers/appStickersManager';
|
||||
import SetTransition from '../singleTransition';
|
||||
import { SuperStickerRenderer } from '../emoticonsDropdown/tabs/stickers';
|
||||
import LazyLoadQueue from '../lazyLoadQueue';
|
||||
import DivAndCaption from '../divAndCaption';
|
||||
import ButtonMenuToggle from '../buttonMenuToggle';
|
||||
import ListenerSetter from '../../helpers/listenerSetter';
|
||||
|
||||
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 StickersHelper {
|
||||
private container: HTMLElement;
|
||||
private stickersContainer: HTMLElement;
|
||||
private scrollable: Scrollable;
|
||||
private superStickerRenderer: SuperStickerRenderer;
|
||||
private lazyLoadQueue: LazyLoadQueue;
|
||||
private lastEmoticon = '';
|
||||
|
||||
constructor(private appendTo: HTMLElement) {
|
||||
|
||||
}
|
||||
|
||||
public checkEmoticon(emoticon: string) {
|
||||
if(this.lastEmoticon == emoticon) return;
|
||||
|
||||
if(this.lastEmoticon && !emoticon) {
|
||||
if(this.container) {
|
||||
SetTransition(this.container, 'is-visible', false, 200/* , () => {
|
||||
this.stickersContainer.innerHTML = '';
|
||||
} */);
|
||||
}
|
||||
}
|
||||
|
||||
this.lastEmoticon = emoticon;
|
||||
if(this.lazyLoadQueue) {
|
||||
this.lazyLoadQueue.clear();
|
||||
}
|
||||
|
||||
if(!emoticon) {
|
||||
return;
|
||||
}
|
||||
|
||||
appStickersManager.getStickersByEmoticon(emoticon)
|
||||
.then(stickers => {
|
||||
if(this.lastEmoticon != emoticon) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.init) {
|
||||
this.init();
|
||||
this.init = null;
|
||||
}
|
||||
|
||||
this.stickersContainer.innerHTML = '';
|
||||
this.lazyLoadQueue.clear();
|
||||
if(stickers.length) {
|
||||
stickers.forEach(sticker => {
|
||||
this.stickersContainer.append(this.superStickerRenderer.renderSticker(sticker as MyDocument));
|
||||
});
|
||||
}
|
||||
|
||||
SetTransition(this.container, 'is-visible', true, 200);
|
||||
this.scrollable.scrollTop = 0;
|
||||
});
|
||||
}
|
||||
|
||||
private init() {
|
||||
this.container = document.createElement('div');
|
||||
this.container.classList.add('stickers-helper', 'z-depth-1');
|
||||
|
||||
this.stickersContainer = document.createElement('div');
|
||||
this.stickersContainer.classList.add('stickers-helper-stickers', 'super-stickers');
|
||||
this.stickersContainer.addEventListener('click', (e) => {
|
||||
if(!findUpClassName(e.target, 'super-sticker')) {
|
||||
return;
|
||||
}
|
||||
|
||||
appImManager.chatInputC.clearInput();
|
||||
EmoticonsDropdown.onMediaClick(e);
|
||||
});
|
||||
|
||||
this.container.append(this.stickersContainer);
|
||||
|
||||
this.scrollable = new Scrollable(this.container);
|
||||
this.lazyLoadQueue = new LazyLoadQueue();
|
||||
this.superStickerRenderer = new SuperStickerRenderer(this.lazyLoadQueue, CHAT_ANIMATION_GROUP);
|
||||
|
||||
this.appendTo.append(this.container);
|
||||
}
|
||||
}
|
||||
|
||||
export class MarkupTooltip {
|
||||
public container: HTMLElement;
|
||||
private wrapper: HTMLElement;
|
||||
private buttons: {[type in MarkdownType]: HTMLElement} = {} as any;
|
||||
private linkBackButton: HTMLElement;
|
||||
private hideTimeout: number;
|
||||
private inputs: HTMLElement[] = [];
|
||||
private addedListener = false;
|
||||
private waitingForMouseUp = false;
|
||||
private linkInput: HTMLInputElement;
|
||||
private savedRange: Range;
|
||||
|
||||
private init() {
|
||||
this.container = document.createElement('div');
|
||||
this.container.classList.add('markup-tooltip', 'z-depth-1', 'hide');
|
||||
|
||||
this.wrapper = document.createElement('div');
|
||||
this.wrapper.classList.add('markup-tooltip-wrapper');
|
||||
|
||||
const tools1 = document.createElement('div');
|
||||
const tools2 = document.createElement('div');
|
||||
tools1.classList.add('markup-tooltip-tools');
|
||||
tools2.classList.add('markup-tooltip-tools');
|
||||
|
||||
const arr = ['bold', 'italic', 'underline', 'strikethrough', 'monospace', 'link'] as (keyof MarkupTooltip['buttons'])[];
|
||||
arr.forEach(c => {
|
||||
const button = ButtonIcon(c, {noRipple: true});
|
||||
tools1.append(this.buttons[c] = button);
|
||||
|
||||
if(c !== 'link') {
|
||||
button.addEventListener('click', () => {
|
||||
appImManager.chatInputC.applyMarkdown(c);
|
||||
});
|
||||
} else {
|
||||
button.addEventListener('click', () => {
|
||||
this.container.classList.add('is-link');
|
||||
|
||||
if(button.classList.contains('active')) {
|
||||
const startContainer = this.savedRange.startContainer;
|
||||
const anchor = startContainer.parentElement as HTMLAnchorElement;
|
||||
this.linkInput.value = anchor.href;
|
||||
} else {
|
||||
this.linkInput.value = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.linkBackButton = ButtonIcon('back', {noRipple: true});
|
||||
this.linkInput = document.createElement('input');
|
||||
this.linkInput.placeholder = 'Enter URL...';
|
||||
this.linkInput.classList.add('input-clear');
|
||||
this.linkInput.addEventListener('keydown', (e) => {
|
||||
if(e.code == 'Enter') {
|
||||
const valid = !this.linkInput.value.length || RichTextProcessor.matchUrl(this.linkInput.value);///^(http)|(https):\/\//i.test(this.linkInput.value);
|
||||
if(!valid) {
|
||||
if(this.linkInput.classList.contains('error')) {
|
||||
this.linkInput.classList.remove('error');
|
||||
void this.linkInput.offsetLeft; // reflow
|
||||
}
|
||||
|
||||
this.linkInput.classList.add('error');
|
||||
} else {
|
||||
cancelEvent(e);
|
||||
this.resetSelection();
|
||||
appImManager.chatInputC.applyMarkdown('link', this.linkInput.value);
|
||||
this.hide();
|
||||
}
|
||||
} else {
|
||||
this.linkInput.classList.remove('error');
|
||||
}
|
||||
});
|
||||
|
||||
this.linkBackButton.addEventListener('click', () => {
|
||||
this.container.classList.remove('is-link');
|
||||
//input.value = '';
|
||||
this.resetSelection();
|
||||
});
|
||||
|
||||
const delimiter1 = document.createElement('span');
|
||||
const delimiter2 = document.createElement('span');
|
||||
delimiter1.classList.add('markup-tooltip-delimiter');
|
||||
delimiter2.classList.add('markup-tooltip-delimiter');
|
||||
tools1.insertBefore(delimiter1, this.buttons.link);
|
||||
tools2.append(this.linkBackButton, delimiter2, this.linkInput);
|
||||
//tools1.insertBefore(delimiter2, this.buttons.link.nextSibling);
|
||||
|
||||
this.wrapper.append(tools1, tools2);
|
||||
this.container.append(this.wrapper);
|
||||
document.body.append(this.container);
|
||||
}
|
||||
|
||||
private resetSelection() {
|
||||
const selection = window.getSelection();
|
||||
selection.removeAllRanges();
|
||||
selection.addRange(this.savedRange);
|
||||
this.inputs[0].focus();
|
||||
}
|
||||
|
||||
public hide() {
|
||||
if(this.init) return;
|
||||
|
||||
this.container.classList.remove('is-visible');
|
||||
document.removeEventListener('mouseup', this.onMouseUp);
|
||||
if(this.hideTimeout) clearTimeout(this.hideTimeout);
|
||||
this.hideTimeout = window.setTimeout(() => {
|
||||
this.hideTimeout = undefined;
|
||||
this.container.classList.add('hide');
|
||||
this.container.classList.remove('is-link');
|
||||
}, 200);
|
||||
}
|
||||
|
||||
public getActiveMarkupButton() {
|
||||
const nodes = getSelectedNodes();
|
||||
const parents = [...new Set(nodes.map(node => node.parentNode))];
|
||||
if(parents.length > 1) return undefined;
|
||||
|
||||
const node = parents[0] as HTMLElement;
|
||||
let currentMarkup: HTMLElement;
|
||||
for(const type in markdownTags) {
|
||||
const tag = markdownTags[type as MarkdownType];
|
||||
if(node.matches(tag.match)) {
|
||||
currentMarkup = this.buttons[type as MarkdownType];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return currentMarkup;
|
||||
}
|
||||
|
||||
public setActiveMarkupButton() {
|
||||
const activeButton = this.getActiveMarkupButton();
|
||||
|
||||
for(const i in this.buttons) {
|
||||
// @ts-ignore
|
||||
const button = this.buttons[i];
|
||||
if(button != activeButton) {
|
||||
button.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
if(activeButton) {
|
||||
activeButton.classList.add('active');
|
||||
}
|
||||
|
||||
return activeButton;
|
||||
}
|
||||
|
||||
public show() {
|
||||
if(this.init) {
|
||||
this.init();
|
||||
this.init = null;
|
||||
}
|
||||
|
||||
const selection = document.getSelection();
|
||||
|
||||
if(!selection.toString().trim().length) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.hideTimeout !== undefined) {
|
||||
clearTimeout(this.hideTimeout);
|
||||
}
|
||||
|
||||
const range = this.savedRange = selection.getRangeAt(0);
|
||||
|
||||
const activeButton = this.setActiveMarkupButton();
|
||||
|
||||
this.container.classList.remove('is-link');
|
||||
const isFirstShow = this.container.classList.contains('hide');
|
||||
if(isFirstShow) {
|
||||
this.container.classList.remove('hide');
|
||||
this.container.classList.add('no-transition');
|
||||
}
|
||||
|
||||
const selectionRect = range.getBoundingClientRect();
|
||||
//const containerRect = this.container.getBoundingClientRect();
|
||||
const sizesRect = this.container.firstElementChild.firstElementChild.getBoundingClientRect();
|
||||
const top = selectionRect.top - sizesRect.height - 8;
|
||||
const left = selectionRect.left + (selectionRect.width - sizesRect.width) / 2;
|
||||
//const top = selectionRect.top - 44 - 8;
|
||||
|
||||
this.container.style.transform = `translate3d(${left}px, ${top}px, 0)`;
|
||||
|
||||
if(isFirstShow) {
|
||||
void this.container.offsetLeft; // reflow
|
||||
this.container.classList.remove('no-transition');
|
||||
}
|
||||
|
||||
this.container.classList.add('is-visible');
|
||||
|
||||
console.log('selection', selectionRect, activeButton);
|
||||
}
|
||||
|
||||
private onMouseUp = (e: Event) => {
|
||||
if(findUpClassName(e.target, 'markup-tooltip')) return;
|
||||
this.hide();
|
||||
document.removeEventListener('mouseup', this.onMouseUp);
|
||||
};
|
||||
|
||||
public setMouseUpEvent() {
|
||||
if(this.waitingForMouseUp) return;
|
||||
this.waitingForMouseUp = true;
|
||||
document.addEventListener('mouseup', (e) => {
|
||||
this.waitingForMouseUp = false;
|
||||
this.show();
|
||||
|
||||
document.addEventListener('mouseup', this.onMouseUp);
|
||||
}, {once: true});
|
||||
}
|
||||
|
||||
public handleSelection(input: HTMLElement) {
|
||||
this.inputs.push(input);
|
||||
|
||||
if(this.addedListener) return;
|
||||
this.addedListener = true;
|
||||
document.addEventListener('selectionchange', (e) => {
|
||||
if(document.activeElement == this.linkInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(!this.inputs.includes(document.activeElement as HTMLElement)) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = document.getSelection();
|
||||
|
||||
if(!selection.toString().trim().length) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
this.setMouseUpEvent();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ChatInput {
|
||||
export default class ChatInput {
|
||||
public pageEl = document.getElementById('page-chats') as HTMLDivElement;
|
||||
public messageInput: HTMLDivElement/* HTMLInputElement */;
|
||||
public fileInput = document.getElementById('input-file') as HTMLInputElement;
|
||||
public inputMessageContainer = document.getElementsByClassName('input-message-container')[0] as HTMLDivElement;
|
||||
public inputScroll = new Scrollable(this.inputMessageContainer);
|
||||
public messageInput: HTMLDivElement;
|
||||
public fileInput: HTMLInputElement;
|
||||
public inputMessageContainer: HTMLDivElement;
|
||||
public inputScroll: Scrollable;
|
||||
public btnSend = document.getElementById('btn-send') as HTMLButtonElement;
|
||||
public btnCancelRecord = this.btnSend.parentElement.previousElementSibling as HTMLButtonElement;
|
||||
public btnCancelRecord: HTMLButtonElement;
|
||||
public lastUrl = '';
|
||||
public lastTimeType = 0;
|
||||
|
||||
private inputContainer = this.btnSend.parentElement.parentElement as HTMLDivElement;
|
||||
private chatInput = this.inputContainer.parentElement as HTMLDivElement;
|
||||
public chatInput: HTMLElement;
|
||||
public inputContainer: HTMLElement;
|
||||
public rowsWrapper: HTMLDivElement;
|
||||
private newMessageWrapper: HTMLDivElement;
|
||||
private btnToggleEmoticons: HTMLButtonElement;
|
||||
private btnSendContainer: HTMLDivElement;
|
||||
|
||||
public attachMenu: HTMLButtonElement;
|
||||
private attachMenuButtons: (ButtonMenuItemOptions & {verify: (peerID: number) => boolean})[];
|
||||
|
||||
public replyElements: {
|
||||
container?: HTMLDivElement,
|
||||
container?: HTMLElement,
|
||||
cancelBtn?: HTMLButtonElement,
|
||||
titleEl?: HTMLDivElement,
|
||||
subtitleEl?: HTMLDivElement
|
||||
titleEl?: HTMLElement,
|
||||
subtitleEl?: HTMLElement
|
||||
} = {};
|
||||
|
||||
public willSendWebPage: any = null;
|
||||
@ -387,8 +71,8 @@ export class ChatInput {
|
||||
private recorder: any;
|
||||
private recording = false;
|
||||
private recordCanceled = false;
|
||||
private recordTimeEl = this.inputContainer.querySelector('.record-time') as HTMLDivElement;
|
||||
private recordRippleEl = this.inputContainer.querySelector('.record-ripple') as HTMLDivElement;
|
||||
private recordTimeEl: HTMLElement;
|
||||
private recordRippleEl: HTMLElement;
|
||||
private recordStartTime = 0;
|
||||
|
||||
private scrollTop = 0;
|
||||
@ -399,7 +83,7 @@ export class ChatInput {
|
||||
private helperFunc: () => void;
|
||||
private helperWaitingForward: boolean;
|
||||
|
||||
private willAttachType: 'document' | 'media';
|
||||
public willAttachType: 'document' | 'media';
|
||||
|
||||
private lockRedo = false;
|
||||
private canRedoFromHTML = '';
|
||||
@ -407,17 +91,42 @@ export class ChatInput {
|
||||
readonly executedHistory: string[] = [];
|
||||
private canUndoFromHTML = '';
|
||||
|
||||
public markupTooltip: MarkupTooltip;
|
||||
public stickersHelper: StickersHelper;
|
||||
public listenerSetter: ListenerSetter;
|
||||
|
||||
constructor() {
|
||||
if(!isTouchSupported) {
|
||||
this.markupTooltip = new MarkupTooltip();
|
||||
}
|
||||
constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appDocsManager: AppDocsManager, private appChatsManager: AppChatsManager, private appPeersManager: AppPeersManager, private appWebPagesManager: AppWebPagesManager, private appImManager: AppImManager) {
|
||||
this.listenerSetter = new ListenerSetter();
|
||||
|
||||
this.attachMessageInputField();
|
||||
this.chatInput = document.createElement('div');
|
||||
this.chatInput.classList.add('chat-input');
|
||||
|
||||
this.attachMenu = document.getElementById('attach-file') as HTMLButtonElement;
|
||||
this.inputContainer = document.createElement('div');
|
||||
this.inputContainer.classList.add('chat-input-container');
|
||||
|
||||
this.rowsWrapper = document.createElement('div');
|
||||
this.rowsWrapper.classList.add('rows-wrapper');
|
||||
|
||||
this.replyElements.container = document.createElement('div');
|
||||
this.replyElements.container.classList.add('reply-wrapper');
|
||||
|
||||
this.replyElements.cancelBtn = ButtonIcon('close reply-cancel');
|
||||
|
||||
const dac = new DivAndCaption('reply');
|
||||
|
||||
this.replyElements.titleEl = dac.title;
|
||||
this.replyElements.subtitleEl = dac.subtitle;
|
||||
|
||||
this.replyElements.container.append(this.replyElements.cancelBtn, dac.container);
|
||||
|
||||
this.newMessageWrapper = document.createElement('div');
|
||||
this.newMessageWrapper.classList.add('new-message-wrapper');
|
||||
|
||||
this.btnToggleEmoticons = ButtonIcon('none toggle-emoticons', {noRipple: true});
|
||||
|
||||
this.inputMessageContainer = document.createElement('div');
|
||||
this.inputMessageContainer.classList.add('input-message-container');
|
||||
|
||||
this.inputScroll = new Scrollable(this.inputMessageContainer);
|
||||
|
||||
this.attachMenuButtons = [{
|
||||
icon: 'photo',
|
||||
@ -443,11 +152,55 @@ export class ChatInput {
|
||||
icon: 'poll',
|
||||
text: 'Poll',
|
||||
onClick: () => {
|
||||
new PopupCreatePoll().show();
|
||||
new PopupCreatePoll(this.chat.peerID).show();
|
||||
},
|
||||
verify: (peerID: number) => peerID < 0 && appChatsManager.hasRights(peerID, 'send', 'send_polls')
|
||||
}];
|
||||
|
||||
this.attachMenu = ButtonMenuToggle({noRipple: true, listenerSetter: this.listenerSetter}, 'top-left', this.attachMenuButtons);
|
||||
this.attachMenu.classList.add('attach-file', 'tgico-attach');
|
||||
this.attachMenu.classList.remove('tgico-more');
|
||||
|
||||
this.recordTimeEl = document.createElement('div');
|
||||
this.recordTimeEl.classList.add('record-time');
|
||||
|
||||
this.fileInput = document.createElement('input');
|
||||
this.fileInput.type = 'file';
|
||||
this.fileInput.multiple = true;
|
||||
this.fileInput.style.display = 'none';
|
||||
|
||||
this.newMessageWrapper.append(this.btnToggleEmoticons, this.inputMessageContainer, this.attachMenu, this.recordTimeEl, this.fileInput);
|
||||
|
||||
this.rowsWrapper.append(this.replyElements.container, this.newMessageWrapper);
|
||||
|
||||
this.btnCancelRecord = ButtonIcon('delete danger btn-circle z-depth-1 btn-record-cancel');
|
||||
|
||||
this.btnSendContainer = document.createElement('div');
|
||||
this.btnSendContainer.classList.add('btn-send-container');
|
||||
|
||||
this.recordRippleEl = document.createElement('div');
|
||||
this.recordRippleEl.classList.add('record-ripple');
|
||||
|
||||
this.btnSend = ButtonIcon('none btn-circle z-depth-1 btn-send');
|
||||
this.btnSend.insertAdjacentHTML('afterbegin', `
|
||||
<span class="tgico tgico-send"></span>
|
||||
<span class="tgico tgico-microphone2"></span>
|
||||
`);
|
||||
|
||||
this.btnSendContainer.append(this.recordRippleEl, this.btnSend);
|
||||
|
||||
this.inputContainer.append(this.rowsWrapper, this.btnCancelRecord, this.btnSendContainer);
|
||||
this.chatInput.append(this.inputContainer);
|
||||
|
||||
// * constructor end
|
||||
|
||||
const toggleClass = isTouchSupported ? 'flip-icon' : 'active';
|
||||
emoticonsDropdown.attachButtonListener(this.btnToggleEmoticons);
|
||||
emoticonsDropdown.events.onOpen.push(this.onEmoticonsOpen);
|
||||
emoticonsDropdown.events.onClose.push(this.onEmoticonsClose);
|
||||
|
||||
this.attachMessageInputField();
|
||||
|
||||
/* this.attachMenu.addEventListener('mousedown', (e) => {
|
||||
const hidden = this.attachMenu.querySelectorAll('.hide');
|
||||
if(hidden.length == this.attachMenuButtons.length) {
|
||||
@ -457,18 +210,7 @@ export class ChatInput {
|
||||
}
|
||||
}, {passive: false, capture: true}); */
|
||||
|
||||
const attachBtnMenu = ButtonMenu(this.attachMenuButtons);
|
||||
attachBtnMenu.classList.add('top-left');
|
||||
this.attachMenu.append(attachBtnMenu);
|
||||
|
||||
ripple(this.attachMenu);
|
||||
|
||||
this.replyElements.container = this.pageEl.querySelector('.reply-wrapper') as HTMLDivElement;
|
||||
this.replyElements.cancelBtn = this.replyElements.container.querySelector('.reply-cancel') as HTMLButtonElement;
|
||||
this.replyElements.titleEl = this.replyElements.container.querySelector('.reply-title') as HTMLDivElement;
|
||||
this.replyElements.subtitleEl = this.replyElements.container.querySelector('.reply-subtitle') as HTMLDivElement;
|
||||
|
||||
this.stickersHelper = new StickersHelper(this.replyElements.container.parentElement);
|
||||
this.stickersHelper = new StickersHelper(this.rowsWrapper);
|
||||
|
||||
try {
|
||||
this.recorder = new Recorder({
|
||||
@ -486,20 +228,7 @@ export class ChatInput {
|
||||
|
||||
this.updateSendBtn();
|
||||
|
||||
rootScope.on('peer_changed', (e) => {
|
||||
const peerID = e.detail;
|
||||
|
||||
const visible = this.attachMenuButtons.filter(button => {
|
||||
const good = button.verify(peerID);
|
||||
button.element.classList.toggle('hide', !good);
|
||||
return good;
|
||||
});
|
||||
|
||||
this.attachMenu.toggleAttribute('disabled', !visible.length);
|
||||
this.updateSendBtn();
|
||||
});
|
||||
|
||||
this.fileInput.addEventListener('change', (e) => {
|
||||
this.listenerSetter.add(this.fileInput, 'change', (e) => {
|
||||
let files = (e.target as HTMLInputElement & EventTarget).files;
|
||||
if(!files.length) {
|
||||
return;
|
||||
@ -509,9 +238,7 @@ export class ChatInput {
|
||||
this.fileInput.value = '';
|
||||
}, false);
|
||||
|
||||
document.addEventListener('paste', this.onDocumentPaste, true);
|
||||
|
||||
this.btnSend.addEventListener(CLICK_EVENT_NAME, this.onBtnSendClick);
|
||||
this.listenerSetter.add(this.btnSend, CLICK_EVENT_NAME, this.onBtnSendClick);
|
||||
|
||||
if(this.recorder) {
|
||||
const onCancelRecordClick = (e: Event) => {
|
||||
@ -520,7 +247,7 @@ export class ChatInput {
|
||||
this.recorder.stop();
|
||||
opusDecodeController.setKeepAlive(false);
|
||||
};
|
||||
this.btnCancelRecord.addEventListener(CLICK_EVENT_NAME, onCancelRecordClick);
|
||||
this.listenerSetter.add(this.btnCancelRecord, CLICK_EVENT_NAME, onCancelRecordClick);
|
||||
|
||||
this.recorder.onstop = () => {
|
||||
this.recording = false;
|
||||
@ -536,33 +263,14 @@ export class ChatInput {
|
||||
const dataBlob = new Blob([typedArray], {type: 'audio/ogg'});
|
||||
/* const fileName = new Date().toISOString() + ".opus";
|
||||
console.log('Recorder data received', typedArray, dataBlob); */
|
||||
|
||||
/* var url = URL.createObjectURL( dataBlob );
|
||||
|
||||
var audio = document.createElement('audio');
|
||||
audio.controls = true;
|
||||
audio.src = url;
|
||||
|
||||
var link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
link.innerHTML = link.download;
|
||||
|
||||
var li = document.createElement('li');
|
||||
li.appendChild(link);
|
||||
li.appendChild(audio);
|
||||
|
||||
document.body.append(li);
|
||||
|
||||
return; */
|
||||
|
||||
|
||||
//let perf = performance.now();
|
||||
opusDecodeController.decode(typedArray, true).then(result => {
|
||||
//console.log('WAVEFORM!:', /* waveform, */performance.now() - perf);
|
||||
|
||||
opusDecodeController.setKeepAlive(false);
|
||||
|
||||
let peerID = appImManager.peerID;
|
||||
let peerID = this.chat.peerID;
|
||||
// тут objectURL ставится уже с audio/wav
|
||||
appMessagesManager.sendFile(peerID, dataBlob, {
|
||||
isVoiceMessage: true,
|
||||
@ -575,28 +283,62 @@ export class ChatInput {
|
||||
|
||||
this.onMessageSent(false, true);
|
||||
});
|
||||
|
||||
/* const url = URL.createObjectURL(dataBlob);
|
||||
|
||||
var audio = document.createElement('audio');
|
||||
audio.controls = true;
|
||||
audio.src = url;
|
||||
|
||||
var link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = fileName;
|
||||
link.innerHTML = link.download;
|
||||
|
||||
var li = document.createElement('li');
|
||||
li.appendChild(link);
|
||||
li.appendChild(audio);
|
||||
|
||||
recordingslist.appendChild(li); */
|
||||
};
|
||||
}
|
||||
|
||||
this.replyElements.cancelBtn.addEventListener(CLICK_EVENT_NAME, this.onHelperCancel);
|
||||
this.replyElements.container.addEventListener(CLICK_EVENT_NAME, this.onHelperClick);
|
||||
this.listenerSetter.add(this.replyElements.cancelBtn, CLICK_EVENT_NAME, this.onHelperCancel);
|
||||
this.listenerSetter.add(this.replyElements.container, CLICK_EVENT_NAME, this.onHelperClick);
|
||||
}
|
||||
|
||||
private onEmoticonsOpen = () => {
|
||||
const toggleClass = isTouchSupported ? 'flip-icon' : 'active';
|
||||
this.btnToggleEmoticons.classList.toggle(toggleClass, true);
|
||||
};
|
||||
|
||||
private onEmoticonsClose = () => {
|
||||
const toggleClass = isTouchSupported ? 'flip-icon' : 'active';
|
||||
this.btnToggleEmoticons.classList.toggle(toggleClass, false);
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
public cleanup() {
|
||||
if(!this.chat.peerID) {
|
||||
this.chatInput.style.display = 'none';
|
||||
}
|
||||
|
||||
cancelSelection();
|
||||
this.clearInput();
|
||||
this.clearHelper();
|
||||
}
|
||||
|
||||
public finishPeerChange() {
|
||||
const peerID = this.chat.peerID;
|
||||
|
||||
const visible = this.attachMenuButtons.filter(button => {
|
||||
const good = button.verify(peerID);
|
||||
button.element.classList.toggle('hide', !good);
|
||||
return good;
|
||||
});
|
||||
|
||||
const canWrite = this.appMessagesManager.canWriteToPeer(peerID);
|
||||
this.chatInput.style.display = '';
|
||||
this.chatInput.classList.toggle('is-hidden', !canWrite);
|
||||
if(!canWrite) {
|
||||
this.messageInput.removeAttribute('contenteditable');
|
||||
} else {
|
||||
this.messageInput.setAttribute('contenteditable', 'true');
|
||||
}
|
||||
|
||||
this.attachMenu.toggleAttribute('disabled', !visible.length);
|
||||
this.updateSendBtn();
|
||||
}
|
||||
|
||||
private attachMessageInputField() {
|
||||
@ -605,7 +347,7 @@ export class ChatInput {
|
||||
name: 'message'
|
||||
});
|
||||
|
||||
messageInputField.input.className = '';
|
||||
messageInputField.input.className = 'input-message-input';
|
||||
this.messageInput = messageInputField.input;
|
||||
this.attachMessageInputListeners();
|
||||
|
||||
@ -618,7 +360,7 @@ export class ChatInput {
|
||||
}
|
||||
|
||||
private attachMessageInputListeners() {
|
||||
this.messageInput.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
this.listenerSetter.add(this.messageInput, 'keydown', (e: KeyboardEvent) => {
|
||||
if(e.key == 'Enter' && !isTouchSupported) {
|
||||
/* if(e.ctrlKey || e.metaKey) {
|
||||
this.messageInput.innerHTML += '<br>';
|
||||
@ -637,18 +379,18 @@ export class ChatInput {
|
||||
});
|
||||
|
||||
if(isTouchSupported) {
|
||||
this.messageInput.addEventListener('touchend', (e) => {
|
||||
appImManager.selectTab(1); // * set chat tab for album orientation
|
||||
this.listenerSetter.add(this.messageInput, 'touchend', (e) => {
|
||||
this.appImManager.selectTab(1); // * set chat tab for album orientation
|
||||
this.saveScroll();
|
||||
emoticonsDropdown.toggle(false);
|
||||
});
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
this.listenerSetter.add(window, 'resize', () => {
|
||||
this.restoreScroll();
|
||||
});
|
||||
}
|
||||
|
||||
this.messageInput.addEventListener('beforeinput', (e: Event) => {
|
||||
this.listenerSetter.add(this.messageInput, '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);
|
||||
@ -661,42 +403,9 @@ export class ChatInput {
|
||||
}
|
||||
}
|
||||
});
|
||||
this.messageInput.addEventListener('input', this.onMessageInput);
|
||||
|
||||
if(this.markupTooltip) {
|
||||
this.markupTooltip.handleSelection(this.messageInput);
|
||||
}
|
||||
this.listenerSetter.add(this.messageInput, 'input', this.onMessageInput);
|
||||
}
|
||||
|
||||
private onDocumentPaste = (e: ClipboardEvent) => {
|
||||
const peerID = rootScope.selectedPeerID;
|
||||
if(!peerID || rootScope.overlayIsActive || (peerID < 0 && !appChatsManager.hasRights(peerID, 'send', 'send_media'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
//console.log('document paste');
|
||||
|
||||
// @ts-ignore
|
||||
const items = (e.clipboardData || e.originalEvent.clipboardData).items;
|
||||
//console.log('item', event.clipboardData.getData());
|
||||
//let foundFile = false;
|
||||
for(let i = 0; i < items.length; ++i) {
|
||||
if(items[i].kind == 'file') {
|
||||
e.preventDefault()
|
||||
e.cancelBubble = true;
|
||||
e.stopPropagation();
|
||||
//foundFile = true;
|
||||
|
||||
let file = items[i].getAsFile();
|
||||
//console.log(items[i], file);
|
||||
if(!file) continue;
|
||||
|
||||
this.willAttachType = file.type.indexOf('image/') === 0 ? 'media' : "document";
|
||||
new PopupNewMedia([file], this.willAttachType);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private prepareDocumentExecute = () => {
|
||||
this.executedHistory.push(this.messageInput.innerHTML);
|
||||
return () => this.canUndoFromHTML = this.messageInput.innerHTML;
|
||||
@ -812,8 +521,8 @@ export class ChatInput {
|
||||
|
||||
checkForSingle();
|
||||
saveExecuted();
|
||||
if(this.markupTooltip) {
|
||||
this.markupTooltip.setActiveMarkupButton();
|
||||
if(this.appImManager.markupTooltip) {
|
||||
this.appImManager.markupTooltip.setActiveMarkupButton();
|
||||
}
|
||||
|
||||
return true;
|
||||
@ -922,7 +631,7 @@ export class ChatInput {
|
||||
url: url,
|
||||
hash: 0
|
||||
}).then((webpage) => {
|
||||
webpage = appWebPagesManager.saveWebPage(webpage);
|
||||
webpage = this.appWebPagesManager.saveWebPage(webpage);
|
||||
if(webpage._ == 'webPage') {
|
||||
if(this.lastUrl != url) return;
|
||||
//console.log('got webpage: ', webpage);
|
||||
@ -952,12 +661,12 @@ export class ChatInput {
|
||||
if(!value.trim() && !serializeNodes(Array.from(this.messageInput.childNodes)).trim()) {
|
||||
this.messageInput.innerHTML = '';
|
||||
|
||||
appMessagesManager.setTyping(rootScope.selectedPeerID, 'sendMessageCancelAction');
|
||||
this.appMessagesManager.setTyping(this.chat.peerID, 'sendMessageCancelAction');
|
||||
} else {
|
||||
const time = Date.now();
|
||||
if(time - this.lastTimeType >= 6000) {
|
||||
this.lastTimeType = time;
|
||||
appMessagesManager.setTyping(rootScope.selectedPeerID, 'sendMessageTypingAction');
|
||||
this.appMessagesManager.setTyping(this.chat.peerID, 'sendMessageTypingAction');
|
||||
}
|
||||
}
|
||||
|
||||
@ -978,7 +687,7 @@ export class ChatInput {
|
||||
this.sendMessage();
|
||||
}
|
||||
} else {
|
||||
if(rootScope.selectedPeerID < 0 && !appChatsManager.hasRights(rootScope.selectedPeerID, 'send', 'send_media')) {
|
||||
if(this.chat.peerID < 0 && !this.appChatsManager.hasRights(this.chat.peerID, 'send', 'send_media')) {
|
||||
toast(POSTING_MEDIA_NOT_ALLOWED);
|
||||
return;
|
||||
}
|
||||
@ -1097,9 +806,9 @@ export class ChatInput {
|
||||
}
|
||||
});
|
||||
} else if(this.helperType == 'reply') {
|
||||
appImManager.setPeer(rootScope.selectedPeerID, this.replyToMsgID);
|
||||
this.chat.setPeer(this.chat.peerID, this.replyToMsgID);
|
||||
} else if(this.helperType == 'edit') {
|
||||
appImManager.setPeer(rootScope.selectedPeerID, this.editMsgID);
|
||||
this.chat.setPeer(this.chat.peerID, this.editMsgID);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1134,9 +843,9 @@ export class ChatInput {
|
||||
}
|
||||
|
||||
public onMessageSent(clearInput = true, clearReply?: boolean) {
|
||||
let dialog = appMessagesManager.getDialogByPeerID(appImManager.peerID)[0];
|
||||
let dialog = this.appMessagesManager.getDialogByPeerID(this.chat.peerID)[0];
|
||||
if(dialog && dialog.top_message) {
|
||||
appMessagesManager.readHistory(appImManager.peerID, dialog.top_message); // lol
|
||||
this.appMessagesManager.readHistory(this.chat.peerID, dialog.top_message); // lol
|
||||
}
|
||||
|
||||
if(clearInput) {
|
||||
@ -1162,11 +871,11 @@ export class ChatInput {
|
||||
//return;
|
||||
|
||||
if(this.editMsgID) {
|
||||
appMessagesManager.editMessage(this.editMsgID, str, {
|
||||
this.appMessagesManager.editMessage(this.editMsgID, str, {
|
||||
noWebPage: this.noWebPage
|
||||
});
|
||||
} else {
|
||||
appMessagesManager.sendText(appImManager.peerID, str, {
|
||||
this.appMessagesManager.sendText(this.chat.peerID, str, {
|
||||
replyToMsgID: this.replyToMsgID == 0 ? undefined : this.replyToMsgID,
|
||||
noWebPage: this.noWebPage,
|
||||
webPage: this.willSendWebPage
|
||||
@ -1177,7 +886,7 @@ export class ChatInput {
|
||||
if(this.forwardingMids.length) {
|
||||
const mids = this.forwardingMids.slice();
|
||||
setTimeout(() => {
|
||||
appMessagesManager.forwardMessages(appImManager.peerID, mids);
|
||||
this.appMessagesManager.forwardMessages(this.chat.peerID, mids);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
@ -1185,9 +894,9 @@ export class ChatInput {
|
||||
}
|
||||
|
||||
public sendMessageWithDocument(document: any) {
|
||||
document = appDocsManager.getDoc(document);
|
||||
document = this.appDocsManager.getDoc(document);
|
||||
if(document && document._ != 'documentEmpty') {
|
||||
appMessagesManager.sendFile(appImManager.peerID, document, {isMedia: true, replyToMsgID: this.replyToMsgID});
|
||||
this.appMessagesManager.sendFile(this.chat.peerID, document, {isMedia: true, replyToMsgID: this.replyToMsgID});
|
||||
this.onMessageSent(false, true);
|
||||
|
||||
if(document.type == 'sticker') {
|
||||
@ -1201,7 +910,7 @@ export class ChatInput {
|
||||
}
|
||||
|
||||
public initMessageEditing(mid: number) {
|
||||
const message = appMessagesManager.getMessage(mid);
|
||||
const message = this.appMessagesManager.getMessage(mid);
|
||||
|
||||
let input = RichTextProcessor.wrapDraftText(message.message, {entities: message.totalEntities});
|
||||
const f = () => {
|
||||
@ -1216,7 +925,7 @@ export class ChatInput {
|
||||
const f = () => {
|
||||
//const peerTitles: string[]
|
||||
const smth: Set<string | number> = new Set(mids.map(mid => {
|
||||
const message = appMessagesManager.getMessage(mid);
|
||||
const message = this.appMessagesManager.getMessage(mid);
|
||||
if(message.fwd_from && message.fwd_from.from_name && !message.fromID && !message.fwdFromID) {
|
||||
return message.fwd_from.from_name;
|
||||
} else {
|
||||
@ -1227,13 +936,13 @@ export class ChatInput {
|
||||
const onlyFirstName = smth.size > 1;
|
||||
const peerTitles = [...smth].map(smth => {
|
||||
return typeof(smth) === 'number' ?
|
||||
appPeersManager.getPeerTitle(smth, true, onlyFirstName) :
|
||||
this.appPeersManager.getPeerTitle(smth, true, onlyFirstName) :
|
||||
(onlyFirstName ? smth.split(' ')[0] : smth);
|
||||
});
|
||||
|
||||
const title = peerTitles.length < 3 ? peerTitles.join(' and ') : peerTitles[0] + ' and ' + (peerTitles.length - 1) + ' others';
|
||||
if(mids.length == 1) {
|
||||
const message = appMessagesManager.getMessage(mids[0]);
|
||||
const message = this.appMessagesManager.getMessage(mids[0]);
|
||||
this.setTopInfo('forward', f, title, message.message, undefined, message);
|
||||
} else {
|
||||
this.setTopInfo('forward', f, title, mids.length + ' forwarded messages');
|
||||
@ -1260,7 +969,7 @@ export class ChatInput {
|
||||
this.forwardingMids.length = 0;
|
||||
this.editMsgID = 0;
|
||||
this.helperType = this.helperFunc = undefined;
|
||||
this.chatInput.parentElement.classList.remove('is-helper-active');
|
||||
this.chat.container.classList.remove('is-helper-active');
|
||||
}
|
||||
|
||||
public setTopInfo(type: ChatInputHelperType, callerFunc: () => void, title = '', subtitle = '', input?: string, message?: any) {
|
||||
@ -1275,7 +984,7 @@ export class ChatInput {
|
||||
this.replyElements.container.append(wrapReply(title, subtitle, message));
|
||||
}
|
||||
|
||||
this.chatInput.parentElement.classList.add('is-helper-active');
|
||||
this.chat.container.classList.add('is-helper-active');
|
||||
/* const scroll = appImManager.scrollable;
|
||||
if(scroll.isScrolledDown && !scroll.scrollLocked && !appImManager.messagesQueuePromise && !appImManager.setPeerPromise) {
|
||||
scroll.scrollTo(scroll.scrollHeight, 'top', true, true, 200);
|
||||
@ -1297,7 +1006,7 @@ export class ChatInput {
|
||||
}
|
||||
|
||||
public saveScroll() {
|
||||
this.scrollTop = appImManager.scrollable.container.scrollTop;
|
||||
this.scrollTop = this.chat.bubbles.scrollable.container.scrollTop;
|
||||
this.scrollOffsetTop = this.chatInput.offsetTop;
|
||||
}
|
||||
|
||||
@ -1305,7 +1014,7 @@ export class ChatInput {
|
||||
if(this.chatInput.style.display) return;
|
||||
//console.log('input resize', offsetTop, this.chatInput.offsetTop);
|
||||
let newOffsetTop = this.chatInput.offsetTop;
|
||||
let container = appImManager.scrollable.container;
|
||||
let container = this.chat.bubbles.scrollable.container;
|
||||
let scrollTop = container.scrollTop;
|
||||
let clientHeight = container.clientHeight;
|
||||
let maxScrollTop = container.scrollHeight;
|
||||
|
244
src/components/chat/markupTooltip.ts
Normal file
244
src/components/chat/markupTooltip.ts
Normal file
@ -0,0 +1,244 @@
|
||||
import type { AppImManager } from "../../lib/appManagers/appImManager";
|
||||
import { MarkdownType, cancelEvent, getSelectedNodes, markdownTags, findUpClassName } from "../../helpers/dom";
|
||||
import RichTextProcessor from "../../lib/richtextprocessor";
|
||||
import ButtonIcon from "../buttonIcon";
|
||||
|
||||
export default class MarkupTooltip {
|
||||
public container: HTMLElement;
|
||||
private wrapper: HTMLElement;
|
||||
private buttons: {[type in MarkdownType]: HTMLElement} = {} as any;
|
||||
private linkBackButton: HTMLElement;
|
||||
private hideTimeout: number;
|
||||
private addedListener = false;
|
||||
private waitingForMouseUp = false;
|
||||
private linkInput: HTMLInputElement;
|
||||
private savedRange: Range;
|
||||
|
||||
constructor(private appImManager: AppImManager) {
|
||||
|
||||
}
|
||||
|
||||
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', () => {
|
||||
this.appImManager.chat.input.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();
|
||||
this.appImManager.chat.input.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.appImManager.chat.input.messageInput.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() {
|
||||
if(this.addedListener) return;
|
||||
this.addedListener = true;
|
||||
document.addEventListener('selectionchange', (e) => {
|
||||
if(document.activeElement == this.linkInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(document.activeElement != this.appImManager.chat.input.messageInput) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
const selection = document.getSelection();
|
||||
|
||||
if(!selection.toString().trim().length) {
|
||||
this.hide();
|
||||
return;
|
||||
}
|
||||
|
||||
this.setMouseUpEvent();
|
||||
});
|
||||
}
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import type Chat from "./chat";
|
||||
import type ChatTopbar from "./topbar";
|
||||
import mediaSizes from "../../helpers/mediaSizes";
|
||||
import appImManager from "../../lib/appManagers/appImManager";
|
||||
import { cancelEvent } from "../../helpers/dom";
|
||||
import DivAndCaption from "../divAndCaption";
|
||||
import { ripple } from "../ripple";
|
||||
@ -12,7 +13,7 @@ export default class PinnedContainer {
|
||||
private close: HTMLElement;
|
||||
protected wrapper: HTMLElement;
|
||||
|
||||
constructor(protected className: string, public divAndCaption: DivAndCaption<(title: string, subtitle: string, message?: any) => void>, onClose?: () => void | Promise<boolean>) {
|
||||
constructor(protected topbar: ChatTopbar, protected chat: Chat, protected className: string, public divAndCaption: DivAndCaption<(title: string, subtitle: string, message?: any) => void>, onClose?: () => void | Promise<boolean>) {
|
||||
/* const prev = this.divAndCaption.fill;
|
||||
this.divAndCaption.fill = (mid, title, subtitle) => {
|
||||
this.divAndCaption.container.dataset.mid = '' + mid;
|
||||
@ -38,7 +39,7 @@ export default class PinnedContainer {
|
||||
|
||||
divAndCaption.container.append(this.close, this.wrapper);
|
||||
|
||||
this.close.addEventListener('click', (e) => {
|
||||
this.topbar.listenerSetter.add(this.close, 'click', (e) => {
|
||||
cancelEvent(e);
|
||||
|
||||
((onClose ? onClose() : null) || Promise.resolve(true)).then(needClose => {
|
||||
@ -59,24 +60,24 @@ export default class PinnedContainer {
|
||||
|
||||
this.divAndCaption.container.classList.toggle('is-floating', mediaSizes.isMobile);
|
||||
|
||||
const scrollTop = mediaSizes.isMobile /* && !appImManager.scrollable.isScrolledDown */ ? appImManager.scrollable.scrollTop : undefined;
|
||||
const scrollTop = mediaSizes.isMobile /* && !appImManager.scrollable.isScrolledDown */ ? this.chat.bubbles.scrollable.scrollTop : undefined;
|
||||
this.divAndCaption.container.classList.toggle('hide', hide);
|
||||
const className = `is-pinned-${this.className}-shown`;
|
||||
appImManager.topbar.classList.toggle(className, !hide);
|
||||
this.topbar.container.classList.toggle(className, !hide);
|
||||
|
||||
const active = classNames.filter(className => appImManager.topbar.classList.contains(className));
|
||||
const active = classNames.filter(className => this.topbar.container.classList.contains(className));
|
||||
const maxActive = hide ? 0 : 1;
|
||||
|
||||
if(scrollTop !== undefined && active.length <= maxActive) {
|
||||
appImManager.scrollable.scrollTop = scrollTop + ((hide ? -1 : 1) * HEIGHT);
|
||||
this.chat.bubbles.scrollable.scrollTop = scrollTop + ((hide ? -1 : 1) * HEIGHT);
|
||||
}
|
||||
|
||||
appImManager.setUtilsWidth();
|
||||
this.topbar.setUtilsWidth();
|
||||
}
|
||||
|
||||
public fill(title: string, subtitle: string, message: any) {
|
||||
this.divAndCaption.container.dataset.mid = '' + message.mid;
|
||||
this.divAndCaption.fill(title, subtitle, message);
|
||||
appImManager.setUtilsWidth();
|
||||
this.topbar.setUtilsWidth();
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,15 @@
|
||||
import type { AppImManager } from "../../lib/appManagers/appImManager";
|
||||
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
|
||||
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
|
||||
import type ChatTopbar from "./topbar";
|
||||
import { ScreenSize } from "../../helpers/mediaSizes";
|
||||
import appPeersManager from "../../lib/appManagers/appPeersManager";
|
||||
import PopupPinMessage from "../popupUnpinMessage";
|
||||
import PinnedContainer from "./pinnedContainer";
|
||||
import PinnedMessageBorder from "./pinnedMessageBorder";
|
||||
import ReplyContainer, { wrapReplyDivAndCaption } from "./replyContainer";
|
||||
import rootScope from "../../lib/rootScope";
|
||||
import { findUpClassName } from "../../helpers/dom";
|
||||
import Chat from "./chat";
|
||||
|
||||
class AnimatedSuper {
|
||||
static DURATION = 200;
|
||||
@ -188,7 +190,7 @@ class AnimatedCounter {
|
||||
}
|
||||
}
|
||||
|
||||
export default class PinnedMessage {
|
||||
export default class ChatPinnedMessage {
|
||||
public pinnedMessageContainer: PinnedContainer;
|
||||
public pinnedMessageBorder: PinnedMessageBorder;
|
||||
public pinnedIndex = 0;
|
||||
@ -200,17 +202,17 @@ export default class PinnedMessage {
|
||||
public animatedMedia: AnimatedSuper;
|
||||
public animatedCounter: AnimatedCounter;
|
||||
|
||||
constructor(private appImManager: AppImManager, private appMessagesManager: AppMessagesManager) {
|
||||
this.pinnedMessageContainer = new PinnedContainer('message', new ReplyContainer('pinned-message'), () => {
|
||||
if(appPeersManager.canPinMessage(this.appImManager.peerID)) {
|
||||
new PopupPinMessage(this.appImManager.peerID, 0);
|
||||
constructor(private topbar: ChatTopbar, private chat: Chat, private appMessagesManager: AppMessagesManager, private appPeersManager: AppPeersManager) {
|
||||
this.pinnedMessageContainer = new PinnedContainer(topbar, chat, 'message', new ReplyContainer('pinned-message'), () => {
|
||||
if(appPeersManager.canPinMessage(this.topbar.peerID)) {
|
||||
new PopupPinMessage(this.topbar.peerID, 0);
|
||||
return Promise.resolve(false);
|
||||
}
|
||||
});
|
||||
|
||||
this.pinnedMessageBorder = new PinnedMessageBorder();
|
||||
this.pinnedMessageContainer.divAndCaption.border.replaceWith(this.pinnedMessageBorder.render(1, 0));
|
||||
this.appImManager.btnJoin.parentElement.insertBefore(this.pinnedMessageContainer.divAndCaption.container, this.appImManager.btnJoin);
|
||||
this.topbar.btnJoin.parentElement.insertBefore(this.pinnedMessageContainer.divAndCaption.container, this.topbar.btnJoin);
|
||||
|
||||
this.animatedSubtitle = new AnimatedSuper();
|
||||
this.pinnedMessageContainer.divAndCaption.subtitle.append(this.animatedSubtitle.container);
|
||||
@ -223,17 +225,17 @@ export default class PinnedMessage {
|
||||
this.pinnedMessageContainer.divAndCaption.title.innerHTML = 'Pinned Message ';
|
||||
this.pinnedMessageContainer.divAndCaption.title.append(this.animatedCounter.container);
|
||||
|
||||
rootScope.on('peer_pinned_messages', (e) => {
|
||||
this.topbar.listenerSetter.add(rootScope, 'peer_pinned_messages', (e) => {
|
||||
const peerID = e.detail;
|
||||
|
||||
if(peerID == this.appImManager.peerID) {
|
||||
if(peerID == this.topbar.peerID) {
|
||||
this.setPinnedMessage();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public setCorrectIndex(lastScrollDirection?: number) {
|
||||
if(this.locked || this.appImManager.setPeerPromise) {
|
||||
if(this.locked || this.chat.setPeerPromise) {
|
||||
return;
|
||||
}/* else if(this.waitForScrollBottom) {
|
||||
if(lastScrollDirection === 1) {
|
||||
@ -244,7 +246,7 @@ export default class PinnedMessage {
|
||||
} */
|
||||
|
||||
///const perf = performance.now();
|
||||
const rect = this.appImManager.scrollable.container.getBoundingClientRect();
|
||||
const rect = this.chat.bubbles.scrollable.container.getBoundingClientRect();
|
||||
const x = Math.ceil(rect.left + ((rect.right - rect.left) / 2) + 1);
|
||||
const y = Math.floor(rect.top + rect.height - 1);
|
||||
let el: HTMLElement = document.elementFromPoint(x, y) as any;
|
||||
@ -255,7 +257,7 @@ export default class PinnedMessage {
|
||||
|
||||
if(el && el.dataset.mid !== undefined) {
|
||||
const mid = +el.dataset.mid;
|
||||
this.appMessagesManager.getPinnedMessages(this.appImManager.peerID).then(mids => {
|
||||
this.appMessagesManager.getPinnedMessages(this.topbar.peerID).then(mids => {
|
||||
let currentIndex = mids.findIndex(_mid => _mid <= mid);
|
||||
if(currentIndex === -1) {
|
||||
currentIndex = mids.length ? mids.length - 1 : 0;
|
||||
@ -292,14 +294,14 @@ export default class PinnedMessage {
|
||||
this.pinnedIndex = index >= (mids.length - 1) ? 0 : index + 1;
|
||||
this.setPinnedMessage();
|
||||
|
||||
const setPeerPromise = this.appImManager.setPeer(message.peerID, mid);
|
||||
const setPeerPromise = this.chat.setPeer(message.peerID, mid);
|
||||
if(setPeerPromise instanceof Promise) {
|
||||
await setPeerPromise;
|
||||
}
|
||||
|
||||
await this.appImManager.scrollable.scrollLockedPromise;
|
||||
await this.chat.bubbles.scrollable.scrollLockedPromise;
|
||||
} catch(err) {
|
||||
this.appImManager.log.error('[PM]: followPinnedMessage error:', err);
|
||||
this.chat.log.error('[PM]: followPinnedMessage error:', err);
|
||||
}
|
||||
|
||||
// подождём, пока скролл остановится
|
||||
@ -318,9 +320,9 @@ export default class PinnedMessage {
|
||||
public setPinnedMessage() {
|
||||
/////this.log('setting pinned message', message);
|
||||
//return;
|
||||
const promise: Promise<any> = this.appImManager.setPeerPromise || this.appImManager.messagesQueuePromise || Promise.resolve();
|
||||
const promise: Promise<any> = this.chat.setPeerPromise || this.chat.bubbles.messagesQueuePromise || Promise.resolve();
|
||||
Promise.all([
|
||||
this.appMessagesManager.getPinnedMessages(this.appImManager.peerID),
|
||||
this.appMessagesManager.getPinnedMessages(this.topbar.peerID),
|
||||
promise
|
||||
]).then(([mids]) => {
|
||||
//const mids = results[0];
|
||||
@ -344,11 +346,12 @@ export default class PinnedMessage {
|
||||
|
||||
const fromTop = pinnedIndex > this.wasPinnedIndex;
|
||||
|
||||
this.appImManager.log('[PM]: setPinnedMessage: fromTop', fromTop, pinnedIndex, this.wasPinnedIndex);
|
||||
this.chat.log('[PM]: setPinnedMessage: fromTop', fromTop, pinnedIndex, this.wasPinnedIndex);
|
||||
|
||||
const writeTo = this.animatedSubtitle.getRow(pinnedIndex);
|
||||
const writeMediaTo = this.animatedMedia.getRow(pinnedIndex);
|
||||
writeMediaTo.classList.add('pinned-message-media');
|
||||
//writeMediaTo.innerHTML = writeMediaTo.style.cssText = writeMediaTo.dataset.docID = '';
|
||||
const isMediaSet = wrapReplyDivAndCaption({
|
||||
title: undefined,
|
||||
titleEl: null,
|
||||
|
@ -128,7 +128,7 @@ export default class PinnedMessageBorder {
|
||||
if(this.count !== count) {
|
||||
this.wrapper.className = 'pinned-message-border-wrapper-1';
|
||||
this.border.classList.remove('pinned-message-border-mask');
|
||||
this.wrapper.innerHTML = '';
|
||||
this.wrapper.innerHTML = this.wrapper.style.cssText = '';
|
||||
}
|
||||
|
||||
return this.border;
|
||||
|
@ -35,18 +35,31 @@ export function wrapReplyDivAndCaption(options: {
|
||||
//console.log('wrap reply', media);
|
||||
|
||||
if(media.photo || (media.document && ['video', 'sticker', 'gif'].indexOf(media.document.type) !== -1)) {
|
||||
/* const middlewareOriginal = appImManager.chat.bubbles.getMiddleware();
|
||||
const middleware = () => {
|
||||
|
||||
}; */
|
||||
|
||||
if(media.document?.type == 'sticker') {
|
||||
if(mediaEl.style.backgroundImage) {
|
||||
mediaEl.style.backgroundImage = '';
|
||||
}
|
||||
|
||||
setMedia = true;
|
||||
wrapSticker({
|
||||
doc: media.document,
|
||||
div: mediaEl,
|
||||
lazyLoadQueue: appImManager.lazyLoadQueue,
|
||||
lazyLoadQueue: appImManager.chat.bubbles.lazyLoadQueue,
|
||||
group: CHAT_ANIMATION_GROUP,
|
||||
//onlyThumb: media.document.sticker == 2,
|
||||
width: 32,
|
||||
height: 32
|
||||
});
|
||||
} else {
|
||||
if(mediaEl.firstElementChild) {
|
||||
mediaEl.innerHTML = '';
|
||||
}
|
||||
|
||||
const photo = media.photo || media.document;
|
||||
|
||||
const cacheContext = appPhotosManager.getCacheContext(photo);
|
||||
|
@ -1,12 +1,13 @@
|
||||
import appImManager from "../../lib/appManagers/appImManager";
|
||||
import type ChatTopbar from "./topbar";
|
||||
import rootScope from "../../lib/rootScope";
|
||||
import { cancelEvent, whichChild, findUpTag } from "../../helpers/dom";
|
||||
import AppSearch, { SearchGroup } from "../appSearch";
|
||||
import PopupDatePicker from "../popupDatepicker";
|
||||
import { ripple } from "../ripple";
|
||||
import SearchInput from "../searchInput";
|
||||
import type Chat from "./chat";
|
||||
|
||||
export class ChatSearch {
|
||||
export default class ChatSearch {
|
||||
private element: HTMLElement;
|
||||
private backBtn: HTMLElement;
|
||||
private searchInput: SearchInput;
|
||||
@ -27,16 +28,16 @@ export class ChatSearch {
|
||||
private selectedIndex = 0;
|
||||
private setPeerPromise: Promise<any>;
|
||||
|
||||
constructor() {
|
||||
constructor(private topbar: ChatTopbar, private chat: Chat) {
|
||||
this.element = document.createElement('div');
|
||||
this.element.classList.add('sidebar-header', 'chat-search', 'chats-container');
|
||||
this.element.classList.add('sidebar-header', 'chat-search', 'chatlist-container');
|
||||
|
||||
this.backBtn = document.createElement('button');
|
||||
this.backBtn.classList.add('btn-icon', 'tgico-back', 'sidebar-close-button');
|
||||
ripple(this.backBtn);
|
||||
|
||||
this.backBtn.addEventListener('click', () => {
|
||||
appImManager.topbar.classList.remove('hide-pinned');
|
||||
this.topbar.container.classList.remove('hide-pinned');
|
||||
this.element.remove();
|
||||
this.searchInput.remove();
|
||||
this.results.remove();
|
||||
@ -46,14 +47,14 @@ export class ChatSearch {
|
||||
this.upBtn.removeEventListener('click', this.onUpClick);
|
||||
this.downBtn.removeEventListener('click', this.onDownClick);
|
||||
this.searchGroup.list.removeEventListener('click', this.onResultsClick);
|
||||
appImManager.bubblesContainer.classList.remove('search-results-active');
|
||||
this.chat.bubbles.bubblesContainer.classList.remove('search-results-active');
|
||||
}, {once: true});
|
||||
|
||||
this.searchInput = new SearchInput('Search');
|
||||
|
||||
// Results
|
||||
this.results = document.createElement('div');
|
||||
this.results.classList.add('chat-search-results', 'chats-container');
|
||||
this.results.classList.add('chat-search-results', 'chatlist-container');
|
||||
|
||||
this.searchGroup = new SearchGroup('', 'messages', undefined, '', false);
|
||||
this.searchGroup.list.addEventListener('click', this.onResultsClick);
|
||||
@ -66,17 +67,17 @@ export class ChatSearch {
|
||||
if(!this.foundCount) {
|
||||
this.foundCountEl.innerText = this.searchInput.value ? 'No results' : '';
|
||||
this.results.classList.remove('active');
|
||||
appImManager.bubblesContainer.classList.remove('search-results-active');
|
||||
this.chat.bubbles.bubblesContainer.classList.remove('search-results-active');
|
||||
this.upBtn.setAttribute('disabled', 'true');
|
||||
this.downBtn.setAttribute('disabled', 'true');
|
||||
} else {
|
||||
this.selectResult(this.searchGroup.list.children[0] as HTMLElement);
|
||||
}
|
||||
});
|
||||
this.appSearch.beginSearch(rootScope.selectedPeerID);
|
||||
this.appSearch.beginSearch(this.chat.peerID);
|
||||
|
||||
//appImManager.topbar.parentElement.insertBefore(this.results, appImManager.bubblesContainer);
|
||||
appImManager.bubblesContainer.append(this.results);
|
||||
this.chat.bubbles.bubblesContainer.append(this.results);
|
||||
|
||||
// Footer
|
||||
this.footer = document.createElement('div');
|
||||
@ -109,20 +110,20 @@ export class ChatSearch {
|
||||
|
||||
this.footer.append(this.foundCountEl, this.dateBtn, this.controls);
|
||||
|
||||
appImManager.topbar.parentElement.insertBefore(this.footer, appImManager.chatInput);
|
||||
this.topbar.container.parentElement.insertBefore(this.footer, chat.input.chatInput);
|
||||
|
||||
// Append container
|
||||
this.element.append(this.backBtn, this.searchInput.container);
|
||||
|
||||
appImManager.topbar.classList.add('hide-pinned');
|
||||
appImManager.topbar.parentElement.append(this.element);
|
||||
this.topbar.container.classList.add('hide-pinned');
|
||||
this.topbar.container.parentElement.append(this.element);
|
||||
|
||||
this.searchInput.input.focus();
|
||||
}
|
||||
|
||||
onDateClick = (e: MouseEvent) => {
|
||||
cancelEvent(e);
|
||||
new PopupDatePicker(new Date(), appImManager.onDatePick).show();
|
||||
new PopupDatePicker(new Date(), this.chat.bubbles.onDatePick).show();
|
||||
};
|
||||
|
||||
selectResult = (elem: HTMLElement) => {
|
||||
@ -146,9 +147,9 @@ export class ChatSearch {
|
||||
}
|
||||
|
||||
this.results.classList.remove('active');
|
||||
appImManager.bubblesContainer.classList.remove('search-results-active');
|
||||
this.chat.bubbles.bubblesContainer.classList.remove('search-results-active');
|
||||
|
||||
const res = appImManager.setPeer(peerID, lastMsgID);
|
||||
const res = this.chat.setPeer(peerID, lastMsgID);
|
||||
this.setPeerPromise = (res instanceof Promise ? res : Promise.resolve(res)).then(() => {
|
||||
this.selectedIndex = index;
|
||||
this.foundCountEl.innerText = `${index + 1} of ${this.foundCount}`;
|
||||
@ -171,7 +172,7 @@ export class ChatSearch {
|
||||
|
||||
onFooterClick = (e: MouseEvent) => {
|
||||
if(this.foundCount) {
|
||||
appImManager.bubblesContainer.classList.toggle('search-results-active');
|
||||
this.chat.bubbles.bubblesContainer.classList.toggle('search-results-active');
|
||||
this.results.classList.toggle('active');
|
||||
}
|
||||
};
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { isTouchSupported } from "../../helpers/touchSupport";
|
||||
import type { AppImManager } from "../../lib/appManagers/appImManager";
|
||||
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
|
||||
import type ChatBubbles from "./bubbles";
|
||||
import type ChatInput from "./input";
|
||||
import { isTouchSupported } from "../../helpers/touchSupport";
|
||||
import { blurActiveElement, cancelEvent, cancelSelection, findUpClassName, getSelectedText } from "../../helpers/dom";
|
||||
import Button from "../button";
|
||||
import ButtonIcon from "../buttonIcon";
|
||||
@ -9,6 +10,7 @@ import PopupDeleteMessages from "../popupDeleteMessages";
|
||||
import PopupForward from "../popupForward";
|
||||
import { toast } from "../toast";
|
||||
import SetTransition from "../singleTransition";
|
||||
import ListenerSetter from "../../helpers/listenerSetter";
|
||||
|
||||
const MAX_SELECTION_LENGTH = 100;
|
||||
//const MIN_CLICK_MOVE = 32; // minimum bubble height
|
||||
@ -24,18 +26,21 @@ export default class ChatSelection {
|
||||
|
||||
public selectedText: string;
|
||||
|
||||
constructor(private appImManager: AppImManager, private appMessagesManager: AppMessagesManager) {
|
||||
const bubblesContainer = appImManager.bubblesContainer;
|
||||
private listenerSetter: ListenerSetter;
|
||||
|
||||
constructor(private chatBubbles: ChatBubbles, private chatInput: ChatInput, private appMessagesManager: AppMessagesManager) {
|
||||
const bubblesContainer = chatBubbles.bubblesContainer;
|
||||
this.listenerSetter = chatBubbles.listenerSetter;
|
||||
|
||||
if(isTouchSupported) {
|
||||
bubblesContainer.addEventListener('touchend', (e) => {
|
||||
this.listenerSetter.add(bubblesContainer, 'touchend', (e) => {
|
||||
if(!this.isSelecting) return;
|
||||
this.selectedText = getSelectedText();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
bubblesContainer.addEventListener('mousedown', (e) => {
|
||||
this.listenerSetter.add(bubblesContainer, 'mousedown', (e) => {
|
||||
//console.log('selection mousedown', e);
|
||||
const bubble = findUpClassName(e.target, 'bubble');
|
||||
// LEFT BUTTON
|
||||
@ -92,8 +97,8 @@ export default class ChatSelection {
|
||||
|
||||
// * cancel selecting if selecting message text
|
||||
if(e.target != bubble && selecting === undefined && !this.selectedMids.size) {
|
||||
bubblesContainer.removeEventListener('mousemove', onMouseMove);
|
||||
document.removeEventListener('mouseup', onMouseUp);
|
||||
this.listenerSetter.removeManual(bubblesContainer, 'mousemove', onMouseMove);
|
||||
this.listenerSetter.removeManual(document, 'mouseup', onMouseUp, documentListenerOptions);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -110,7 +115,7 @@ export default class ChatSelection {
|
||||
if(!this.selectedMids.size) {
|
||||
if(seen.size == 2) {
|
||||
[...seen].forEach(mid => {
|
||||
const mounted = this.appImManager.getMountedBubble(mid);
|
||||
const mounted = this.chatBubbles.getMountedBubble(mid);
|
||||
if(mounted) {
|
||||
this.toggleByBubble(mounted.bubble);
|
||||
}
|
||||
@ -131,15 +136,16 @@ export default class ChatSelection {
|
||||
}, {capture: true, once: true, passive: false});
|
||||
}
|
||||
|
||||
bubblesContainer.removeEventListener('mousemove', onMouseMove);
|
||||
this.listenerSetter.removeManual(bubblesContainer, 'mousemove', onMouseMove);
|
||||
//bubblesContainer.classList.remove('no-select');
|
||||
|
||||
// ! CANCEL USER SELECTION !
|
||||
cancelSelection();
|
||||
};
|
||||
|
||||
bubblesContainer.addEventListener('mousemove', onMouseMove);
|
||||
document.addEventListener('mouseup', onMouseUp, {once: true});
|
||||
const documentListenerOptions = {once: true};
|
||||
this.listenerSetter.add(bubblesContainer, 'mousemove', onMouseMove);
|
||||
this.listenerSetter.add(document, 'mouseup', onMouseUp, documentListenerOptions);
|
||||
});
|
||||
}
|
||||
|
||||
@ -165,7 +171,7 @@ export default class ChatSelection {
|
||||
}
|
||||
|
||||
if(isAlbum) {
|
||||
this.appImManager.getBubbleAlbumItems(bubble).forEach(item => this.toggleBubbleCheckbox(item, show));
|
||||
this.chatBubbles.getBubbleAlbumItems(bubble).forEach(item => this.toggleBubbleCheckbox(item, show));
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,7 +213,7 @@ export default class ChatSelection {
|
||||
|
||||
if(wasSelecting == this.isSelecting) return;
|
||||
|
||||
const bubblesContainer = this.appImManager.bubblesContainer;
|
||||
const bubblesContainer = this.chatBubbles.bubblesContainer;
|
||||
//bubblesContainer.classList.toggle('is-selecting', !!this.selectedMids.size);
|
||||
|
||||
/* if(bubblesContainer.classList.contains('is-chat-input-hidden')) {
|
||||
@ -236,7 +242,7 @@ export default class ChatSelection {
|
||||
}
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
this.appImManager.onScroll();
|
||||
this.chatBubbles.onScroll();
|
||||
});
|
||||
});
|
||||
|
||||
@ -244,19 +250,18 @@ export default class ChatSelection {
|
||||
|
||||
if(this.isSelecting) {
|
||||
if(!this.selectionContainer) {
|
||||
const inputMessageDiv = document.querySelector('.input-message');
|
||||
this.selectionContainer = document.createElement('div');
|
||||
this.selectionContainer.classList.add('selection-container');
|
||||
|
||||
const btnCancel = ButtonIcon('close', {noRipple: true});
|
||||
btnCancel.addEventListener('click', this.cancelSelection, {once: true});
|
||||
this.listenerSetter.add(btnCancel, 'click', this.cancelSelection, {once: true});
|
||||
|
||||
this.selectionCountEl = document.createElement('div');
|
||||
this.selectionCountEl.classList.add('selection-container-count');
|
||||
|
||||
this.selectionForwardBtn = Button('btn-primary btn-transparent selection-container-forward', {icon: 'forward'});
|
||||
this.selectionForwardBtn.append('Forward');
|
||||
this.selectionForwardBtn.addEventListener('click', () => {
|
||||
this.listenerSetter.add(this.selectionForwardBtn, 'click', () => {
|
||||
new PopupForward([...this.selectedMids], () => {
|
||||
this.cancelSelection();
|
||||
});
|
||||
@ -264,7 +269,7 @@ export default class ChatSelection {
|
||||
|
||||
this.selectionDeleteBtn = Button('btn-primary btn-transparent danger selection-container-delete', {icon: 'delete'});
|
||||
this.selectionDeleteBtn.append('Delete');
|
||||
this.selectionDeleteBtn.addEventListener('click', () => {
|
||||
this.listenerSetter.add(this.selectionDeleteBtn, 'click', () => {
|
||||
new PopupDeleteMessages([...this.selectedMids], () => {
|
||||
this.cancelSelection();
|
||||
});
|
||||
@ -272,13 +277,13 @@ export default class ChatSelection {
|
||||
|
||||
this.selectionContainer.append(btnCancel, this.selectionCountEl, this.selectionForwardBtn, this.selectionDeleteBtn);
|
||||
|
||||
inputMessageDiv.append(this.selectionContainer);
|
||||
this.chatInput.rowsWrapper.append(this.selectionContainer);
|
||||
}
|
||||
}
|
||||
|
||||
if(toggleCheckboxes) {
|
||||
for(const mid in this.appImManager.bubbles) {
|
||||
const bubble = this.appImManager.bubbles[mid];
|
||||
for(const mid in this.chatBubbles.bubbles) {
|
||||
const bubble = this.chatBubbles.bubbles[mid];
|
||||
this.toggleBubbleCheckbox(bubble, this.isSelecting);
|
||||
}
|
||||
}
|
||||
@ -286,7 +291,7 @@ export default class ChatSelection {
|
||||
|
||||
public cancelSelection = () => {
|
||||
for(const mid of this.selectedMids) {
|
||||
const mounted = this.appImManager.getMountedBubble(mid);
|
||||
const mounted = this.chatBubbles.getMountedBubble(mid);
|
||||
if(mounted) {
|
||||
this.toggleByBubble(mounted.message.grouped_id ? mounted.bubble.querySelector(`.album-item[data-mid="${mid}"]`) : mounted.bubble);
|
||||
}
|
||||
@ -337,7 +342,7 @@ export default class ChatSelection {
|
||||
mids.forEach(mid => this.selectedMids.delete(mid));
|
||||
}
|
||||
|
||||
this.appImManager.getBubbleAlbumItems(bubble).forEach(this.toggleByBubble);
|
||||
this.chatBubbles.getBubbleAlbumItems(bubble).forEach(this.toggleByBubble);
|
||||
return;
|
||||
}
|
||||
|
||||
|
90
src/components/chat/stickersHelper.ts
Normal file
90
src/components/chat/stickersHelper.ts
Normal file
@ -0,0 +1,90 @@
|
||||
import { findUpClassName } from "../../helpers/dom";
|
||||
import { MyDocument } from "../../lib/appManagers/appDocsManager";
|
||||
import appImManager, { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
|
||||
import appStickersManager from "../../lib/appManagers/appStickersManager";
|
||||
import { EmoticonsDropdown } from "../emoticonsDropdown";
|
||||
import { SuperStickerRenderer } from "../emoticonsDropdown/tabs/stickers";
|
||||
import LazyLoadQueue from "../lazyLoadQueue";
|
||||
import Scrollable from "../scrollable";
|
||||
import SetTransition from "../singleTransition";
|
||||
|
||||
export default class StickersHelper {
|
||||
private container: HTMLElement;
|
||||
private stickersContainer: HTMLElement;
|
||||
private scrollable: Scrollable;
|
||||
private superStickerRenderer: SuperStickerRenderer;
|
||||
private lazyLoadQueue: LazyLoadQueue;
|
||||
private lastEmoticon = '';
|
||||
|
||||
constructor(private appendTo: HTMLElement) {
|
||||
|
||||
}
|
||||
|
||||
public checkEmoticon(emoticon: string) {
|
||||
if(this.lastEmoticon == emoticon) return;
|
||||
|
||||
if(this.lastEmoticon && !emoticon) {
|
||||
if(this.container) {
|
||||
SetTransition(this.container, 'is-visible', false, 200/* , () => {
|
||||
this.stickersContainer.innerHTML = '';
|
||||
} */);
|
||||
}
|
||||
}
|
||||
|
||||
this.lastEmoticon = emoticon;
|
||||
if(this.lazyLoadQueue) {
|
||||
this.lazyLoadQueue.clear();
|
||||
}
|
||||
|
||||
if(!emoticon) {
|
||||
return;
|
||||
}
|
||||
|
||||
appStickersManager.getStickersByEmoticon(emoticon)
|
||||
.then(stickers => {
|
||||
if(this.lastEmoticon != emoticon) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.init) {
|
||||
this.init();
|
||||
this.init = null;
|
||||
}
|
||||
|
||||
this.stickersContainer.innerHTML = '';
|
||||
this.lazyLoadQueue.clear();
|
||||
if(stickers.length) {
|
||||
stickers.forEach(sticker => {
|
||||
this.stickersContainer.append(this.superStickerRenderer.renderSticker(sticker as MyDocument));
|
||||
});
|
||||
}
|
||||
|
||||
SetTransition(this.container, 'is-visible', true, 200);
|
||||
this.scrollable.scrollTop = 0;
|
||||
});
|
||||
}
|
||||
|
||||
private init() {
|
||||
this.container = document.createElement('div');
|
||||
this.container.classList.add('stickers-helper', 'z-depth-1');
|
||||
|
||||
this.stickersContainer = document.createElement('div');
|
||||
this.stickersContainer.classList.add('stickers-helper-stickers', 'super-stickers');
|
||||
this.stickersContainer.addEventListener('click', (e) => {
|
||||
if(!findUpClassName(e.target, 'super-sticker')) {
|
||||
return;
|
||||
}
|
||||
|
||||
appImManager.chat.input.clearInput();
|
||||
EmoticonsDropdown.onMediaClick(e);
|
||||
});
|
||||
|
||||
this.container.append(this.stickersContainer);
|
||||
|
||||
this.scrollable = new Scrollable(this.container);
|
||||
this.lazyLoadQueue = new LazyLoadQueue();
|
||||
this.superStickerRenderer = new SuperStickerRenderer(this.lazyLoadQueue, CHAT_ANIMATION_GROUP);
|
||||
|
||||
this.appendTo.append(this.container);
|
||||
}
|
||||
}
|
345
src/components/chat/topbar.ts
Normal file
345
src/components/chat/topbar.ts
Normal file
@ -0,0 +1,345 @@
|
||||
import type { AppChatsManager, Channel } from "../../lib/appManagers/appChatsManager";
|
||||
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
|
||||
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
|
||||
import type { AppProfileManager } from "../../lib/appManagers/appProfileManager";
|
||||
import type { AppUsersManager } from "../../lib/appManagers/appUsersManager";
|
||||
import type { AppSidebarRight } from "../sidebarRight";
|
||||
import type Chat from "./chat";
|
||||
import { findUpClassName, cancelEvent, attachClickEvent } from "../../helpers/dom";
|
||||
import mediaSizes, { ScreenSize } from "../../helpers/mediaSizes";
|
||||
import { isSafari } from "../../helpers/userAgent";
|
||||
import rootScope from "../../lib/rootScope";
|
||||
import AvatarElement from "../avatar";
|
||||
import Button from "../button";
|
||||
import ButtonIcon from "../buttonIcon";
|
||||
import ButtonMenuToggle from "../buttonMenuToggle";
|
||||
import ChatAudio from "./audio";
|
||||
import ChatPinnedMessage from "./pinnedMessage";
|
||||
import ChatSearch from "./search";
|
||||
import { ButtonMenuItemOptions } from "../buttonMenu";
|
||||
import ListenerSetter from "../../helpers/listenerSetter";
|
||||
|
||||
export default class ChatTopbar {
|
||||
container: HTMLDivElement;
|
||||
btnBack: HTMLButtonElement;
|
||||
chatInfo: HTMLDivElement;
|
||||
avatarElement: AvatarElement;
|
||||
title: HTMLDivElement;
|
||||
subtitle: HTMLDivElement;
|
||||
chatUtils: HTMLDivElement;
|
||||
btnJoin: HTMLButtonElement;
|
||||
btnMute: HTMLButtonElement;
|
||||
btnSearch: HTMLButtonElement;
|
||||
btnMore: HTMLButtonElement;
|
||||
|
||||
public chatAudio: ChatAudio;
|
||||
public pinnedMessage: ChatPinnedMessage;
|
||||
|
||||
private setUtilsRAF: number;
|
||||
public peerID: number;
|
||||
private setPeerStatusInterval: number;
|
||||
|
||||
public listenerSetter: ListenerSetter;
|
||||
|
||||
constructor(private chat: Chat, private appSidebarRight: AppSidebarRight, private appMessagesManager: AppMessagesManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager, private appUsersManager: AppUsersManager, private appProfileManager: AppProfileManager) {
|
||||
this.chat.log.error('Topbar construction');
|
||||
|
||||
this.listenerSetter = new ListenerSetter();
|
||||
|
||||
this.container = document.createElement('div');
|
||||
this.container.classList.add('sidebar-header', 'topbar');
|
||||
|
||||
this.btnBack = ButtonIcon('back sidebar-close-button', {noRipple: true});
|
||||
|
||||
// * chat info section
|
||||
this.chatInfo = document.createElement('div');
|
||||
this.chatInfo.classList.add('chat-info');
|
||||
|
||||
const person = document.createElement('div');
|
||||
person.classList.add('person');
|
||||
|
||||
this.avatarElement = new AvatarElement();
|
||||
this.avatarElement.setAttribute('dialog', '1');
|
||||
this.avatarElement.setAttribute('clickable', '');
|
||||
|
||||
const content = document.createElement('div');
|
||||
content.classList.add('content');
|
||||
|
||||
const top = document.createElement('div');
|
||||
top.classList.add('top');
|
||||
|
||||
this.title = document.createElement('div');
|
||||
this.title.classList.add('user-title');
|
||||
|
||||
top.append(this.title);
|
||||
|
||||
const bottom = document.createElement('div');
|
||||
bottom.classList.add('bottom');
|
||||
|
||||
this.subtitle = document.createElement('div');
|
||||
this.subtitle.classList.add('info');
|
||||
|
||||
bottom.append(this.subtitle);
|
||||
|
||||
content.append(top, bottom);
|
||||
person.append(this.avatarElement, content);
|
||||
this.chatInfo.append(person);
|
||||
|
||||
// * chat utils section
|
||||
this.chatUtils = document.createElement('div');
|
||||
this.chatUtils.classList.add('chat-utils');
|
||||
|
||||
this.chatAudio = new ChatAudio(this, this.chat, this.appMessagesManager, this.appPeersManager);
|
||||
|
||||
this.btnJoin = Button('btn-primary chat-join hide');
|
||||
this.btnJoin.append('SUBSCRIBE');
|
||||
|
||||
this.btnMute = ButtonIcon('mute');
|
||||
this.btnSearch = ButtonIcon('search');
|
||||
const menuButtons: (ButtonMenuItemOptions & {verify: () => boolean})[] = [{
|
||||
icon: 'search',
|
||||
text: 'Search',
|
||||
onClick: () => {
|
||||
new ChatSearch(this, this.chat);
|
||||
},
|
||||
verify: () => mediaSizes.isMobile
|
||||
}, {
|
||||
icon: 'mute',
|
||||
text: 'Mute',
|
||||
onClick: () => {
|
||||
this.appMessagesManager.mutePeer(this.peerID);
|
||||
},
|
||||
verify: () => rootScope.myID != this.peerID && !this.appMessagesManager.isPeerMuted(this.peerID)
|
||||
}, {
|
||||
icon: 'unmute',
|
||||
text: 'Unmute',
|
||||
onClick: () => {
|
||||
this.appMessagesManager.mutePeer(this.peerID);
|
||||
},
|
||||
verify: () => rootScope.myID != this.peerID && this.appMessagesManager.isPeerMuted(this.peerID)
|
||||
}, {
|
||||
icon: 'delete danger',
|
||||
text: 'Delete and Leave',
|
||||
onClick: () => {},
|
||||
verify: () => true
|
||||
}];
|
||||
//menuButtons.forEach(b => b.options = {listenerSetter: this.listenerSetter});
|
||||
this.btnMore = ButtonMenuToggle({listenerSetter: this.listenerSetter}, 'bottom-left', menuButtons, () => {
|
||||
menuButtons.forEach(button => {
|
||||
button.element.classList.toggle('hide', !button.verify());
|
||||
});
|
||||
});
|
||||
|
||||
this.chatUtils.append(this.chatAudio.divAndCaption.container, this.btnJoin, this.btnMute, this.btnSearch, this.btnMore);
|
||||
|
||||
this.container.append(this.btnBack, this.chatInfo, this.chatUtils);
|
||||
|
||||
// * construction end
|
||||
|
||||
// * fix topbar overflow section
|
||||
|
||||
this.listenerSetter.add(window, 'resize', this.onResize);
|
||||
mediaSizes.addListener('changeScreen', this.onChangeScreen);
|
||||
|
||||
this.pinnedMessage = new ChatPinnedMessage(this, this.chat, this.appMessagesManager, this.appPeersManager);
|
||||
|
||||
this.listenerSetter.add(this.container, 'click', (e) => {
|
||||
const pinned: HTMLElement = findUpClassName(e.target, 'pinned-container');
|
||||
if(pinned) {
|
||||
cancelEvent(e);
|
||||
|
||||
const mid = +pinned.dataset.mid;
|
||||
if(pinned.classList.contains('pinned-message')) {
|
||||
this.pinnedMessage.followPinnedMessage(mid);
|
||||
} else {
|
||||
const message = this.appMessagesManager.getMessage(mid);
|
||||
|
||||
this.chat.setPeer(message.peerID, mid);
|
||||
}
|
||||
} else {
|
||||
this.appSidebarRight.toggleSidebar(true);
|
||||
}
|
||||
});
|
||||
|
||||
this.listenerSetter.add(this.btnBack, 'click', (e) => {
|
||||
cancelEvent(e);
|
||||
this.chat.appImManager.setPeer(0);
|
||||
});
|
||||
|
||||
this.listenerSetter.add(this.btnSearch, 'click', (e) => {
|
||||
cancelEvent(e);
|
||||
if(this.peerID) {
|
||||
appSidebarRight.searchTab.open(this.peerID);
|
||||
}
|
||||
});
|
||||
|
||||
this.listenerSetter.add(this.btnMute, 'click', (e) => {
|
||||
cancelEvent(e);
|
||||
this.appMessagesManager.mutePeer(this.peerID);
|
||||
});
|
||||
|
||||
//this.listenerSetter.add(this.btnJoin, 'mousedown', (e) => {
|
||||
attachClickEvent(this.btnJoin, (e) => {
|
||||
cancelEvent(e);
|
||||
|
||||
this.btnJoin.setAttribute('disabled', 'true');
|
||||
appChatsManager.joinChannel(-this.peerID).finally(() => {
|
||||
this.btnJoin.removeAttribute('disabled');
|
||||
});
|
||||
//});
|
||||
}, {listenerSetter: this.listenerSetter});
|
||||
|
||||
this.listenerSetter.add(rootScope, 'chat_update', (e) => {
|
||||
const peerID: number = e.detail;
|
||||
if(this.peerID == -peerID) {
|
||||
const chat = appChatsManager.getChat(peerID) as Channel/* | Chat */;
|
||||
|
||||
this.btnJoin.classList.toggle('hide', !(chat as Channel)?.pFlags?.left);
|
||||
this.setUtilsWidth();
|
||||
}
|
||||
});
|
||||
|
||||
this.listenerSetter.add(rootScope, 'dialog_notify_settings', (e) => {
|
||||
const peerID = e.detail;
|
||||
|
||||
if(peerID == this.peerID) {
|
||||
this.setMutedState();
|
||||
}
|
||||
});
|
||||
|
||||
this.listenerSetter.add(rootScope, 'peer_typings', (e) => {
|
||||
const {peerID} = e.detail;
|
||||
|
||||
if(this.peerID == peerID) {
|
||||
this.setPeerStatus();
|
||||
}
|
||||
});
|
||||
|
||||
this.listenerSetter.add(rootScope, 'user_update', (e) => {
|
||||
const userID = e.detail;
|
||||
|
||||
if(this.peerID == userID) {
|
||||
this.setPeerStatus();
|
||||
}
|
||||
});
|
||||
|
||||
this.setPeerStatusInterval = window.setInterval(this.setPeerStatus, 60e3);
|
||||
}
|
||||
|
||||
private onResize = () => {
|
||||
this.setUtilsWidth(true);
|
||||
};
|
||||
|
||||
private onChangeScreen = (from: ScreenSize, to: ScreenSize) => {
|
||||
this.chatAudio.divAndCaption.container.classList.toggle('is-floating', to == ScreenSize.mobile);
|
||||
this.pinnedMessage.onChangeScreen(from, to);
|
||||
this.setUtilsWidth(true);
|
||||
};
|
||||
|
||||
public destroy() {
|
||||
this.chat.log.error('Topbar destroying');
|
||||
|
||||
this.listenerSetter.removeAll();
|
||||
mediaSizes.removeListener('changeScreen', this.onChangeScreen);
|
||||
window.clearInterval(this.setPeerStatusInterval);
|
||||
|
||||
delete this.chatAudio;
|
||||
delete this.pinnedMessage;
|
||||
}
|
||||
|
||||
public setPeer(peerID: number) {
|
||||
this.peerID = peerID;
|
||||
|
||||
this.avatarElement.setAttribute('peer', '' + peerID);
|
||||
this.avatarElement.update();
|
||||
|
||||
this.container.classList.remove('is-pinned-shown');
|
||||
this.container.style.display = peerID ? '' : 'none';
|
||||
|
||||
const isBroadcast = this.appPeersManager.isBroadcast(peerID);
|
||||
|
||||
this.btnMute.classList.toggle('hide', !isBroadcast);
|
||||
this.btnJoin.classList.toggle('hide', !this.appChatsManager.getChat(-peerID)?.pFlags?.left);
|
||||
this.setUtilsWidth();
|
||||
|
||||
window.requestAnimationFrame(() => {
|
||||
this.pinnedMessage.pinnedIndex/* = this.pinnedMessage.wasPinnedIndex */ = 0;
|
||||
//this.pinnedMessage.setCorrectIndex();
|
||||
this.pinnedMessage.setPinnedMessage();
|
||||
/* noTransition.forEach(el => {
|
||||
el.classList.remove('no-transition-all');
|
||||
}); */
|
||||
/* if(needToChangeInputDisplay) {
|
||||
this.chatInput.style.display = '';
|
||||
} */
|
||||
|
||||
let title = '';
|
||||
if(peerID == rootScope.myID) title = 'Saved Messages';
|
||||
else title = this.appPeersManager.getPeerTitle(peerID);
|
||||
this.title.innerHTML = title;
|
||||
|
||||
this.setPeerStatus(true);
|
||||
this.setMutedState();
|
||||
});
|
||||
}
|
||||
|
||||
public setMutedState() {
|
||||
const peerID = this.peerID;
|
||||
let muted = this.appMessagesManager.isPeerMuted(peerID);
|
||||
if(this.appPeersManager.isBroadcast(peerID)) { // not human
|
||||
this.btnMute.classList.remove('tgico-mute', 'tgico-unmute');
|
||||
this.btnMute.classList.add(muted ? 'tgico-unmute' : 'tgico-mute');
|
||||
this.btnMute.style.display = '';
|
||||
} else {
|
||||
this.btnMute.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// ! У МЕНЯ ПРОСТО СГОРЕЛО, САФАРИ КОНЧЕННЫЙ БРАУЗЕР - ЕСЛИ НЕ СКРЫВАТЬ БЛОК, ТО ПРИ ПЕРЕВОРОТЕ ЭКРАНА НА АЙФОНЕ БЛОК БУДЕТ НЕПРАВИЛЬНО ШИРИНЫ, ДАЖЕ БЕЗ ЭТОЙ ФУНКЦИИ!
|
||||
public setUtilsWidth = (resize = false) => {
|
||||
//return;
|
||||
if(this.setUtilsRAF) window.cancelAnimationFrame(this.setUtilsRAF);
|
||||
|
||||
if(isSafari && resize) {
|
||||
this.chatUtils.classList.add('hide');
|
||||
}
|
||||
|
||||
//mutationObserver.disconnect();
|
||||
this.setUtilsRAF = window.requestAnimationFrame(() => {
|
||||
|
||||
//mutationRAF = window.requestAnimationFrame(() => {
|
||||
|
||||
//setTimeout(() => {
|
||||
if(isSafari && resize) {
|
||||
this.chatUtils.classList.remove('hide');
|
||||
}
|
||||
/* this.chatInfo.style.removeProperty('--utils-width');
|
||||
void this.chatInfo.offsetLeft; // reflow */
|
||||
const width = /* chatUtils.scrollWidth */this.chatUtils.getBoundingClientRect().width;
|
||||
this.chat.log('utils width:', width);
|
||||
this.chatInfo.style.setProperty('--utils-width', width + 'px');
|
||||
//this.chatInfo.classList.toggle('have-utils-width', !!width);
|
||||
//}, 0);
|
||||
|
||||
this.setUtilsRAF = 0;
|
||||
|
||||
//mutationObserver.observe(chatUtils, observeOptions);
|
||||
//});
|
||||
});
|
||||
};
|
||||
|
||||
public setPeerStatus = (needClear = false) => {
|
||||
const peerID = this.peerID;
|
||||
if(needClear) {
|
||||
this.subtitle.innerHTML = '';
|
||||
}
|
||||
|
||||
this.chat.appImManager.getPeerStatus(this.peerID).then((subtitle) => {
|
||||
if(peerID != this.peerID) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.subtitle.innerHTML = subtitle;
|
||||
});
|
||||
};
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import appChatsManager from "../lib/appManagers/appChatsManager";
|
||||
import appDialogsManager from "../lib/appManagers/appDialogsManager";
|
||||
import appImManager from "../lib/appManagers/appImManager";
|
||||
import appMessagesManager, {Dialog} from "../lib/appManagers/appMessagesManager";
|
||||
import appPeersManager from "../lib/appManagers/appPeersManager";
|
||||
import rootScope from "../lib/rootScope";
|
||||
@ -102,7 +101,7 @@ export default class DialogsContextMenu {
|
||||
};
|
||||
|
||||
private onMuteClick = () => {
|
||||
appImManager.mutePeer(this.selectedID);
|
||||
appMessagesManager.mutePeer(this.selectedID);
|
||||
};
|
||||
|
||||
private onUnreadClick = () => {
|
||||
|
@ -5,7 +5,7 @@ export default class DivAndCaption<T> {
|
||||
public title: HTMLElement;
|
||||
public subtitle: HTMLElement;
|
||||
|
||||
constructor(protected className: string, public fill: T) {
|
||||
constructor(protected className: string, public fill?: T) {
|
||||
this.container = document.createElement('div');
|
||||
this.container.className = className;
|
||||
|
||||
|
@ -40,7 +40,6 @@ export class EmoticonsDropdown {
|
||||
public searchButton: HTMLElement;
|
||||
public deleteBtn: HTMLElement;
|
||||
|
||||
public toggleEl: HTMLElement;
|
||||
private displayTimeout: number;
|
||||
|
||||
public events: {
|
||||
@ -57,26 +56,28 @@ export class EmoticonsDropdown {
|
||||
|
||||
private selectTab: ReturnType<typeof horizontalMenu>;
|
||||
|
||||
private firstTime = true;
|
||||
|
||||
constructor() {
|
||||
this.element = document.getElementById('emoji-dropdown') as HTMLDivElement;
|
||||
}
|
||||
|
||||
let firstTime = true;
|
||||
this.toggleEl = document.getElementById('toggle-emoticons');
|
||||
public attachButtonListener(button: HTMLElement) {
|
||||
if(isTouchSupported) {
|
||||
this.toggleEl.addEventListener('click', () => {
|
||||
if(firstTime) {
|
||||
firstTime = false;
|
||||
button.addEventListener('click', () => {
|
||||
if(this.firstTime) {
|
||||
this.firstTime = false;
|
||||
this.toggle(true);
|
||||
} else {
|
||||
this.toggle();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.toggleEl.onmouseover = (e) => {
|
||||
button.onmouseover = (e) => {
|
||||
clearTimeout(this.displayTimeout);
|
||||
//this.displayTimeout = setTimeout(() => {
|
||||
if(firstTime) {
|
||||
this.toggleEl.onmouseout = this.element.onmouseout = (e) => {
|
||||
if(this.firstTime) {
|
||||
button.onmouseout = this.element.onmouseout = (e) => {
|
||||
if(test) return;
|
||||
if(!this.element.classList.contains('active')) return;
|
||||
|
||||
@ -95,7 +96,7 @@ export class EmoticonsDropdown {
|
||||
clearTimeout(this.displayTimeout);
|
||||
};
|
||||
|
||||
firstTime = false;
|
||||
this.firstTime = false;
|
||||
}
|
||||
|
||||
this.toggle(true);
|
||||
@ -138,7 +139,7 @@ export class EmoticonsDropdown {
|
||||
|
||||
this.deleteBtn = this.element.querySelector('.emoji-tabs-delete');
|
||||
this.deleteBtn.addEventListener('click', () => {
|
||||
const input = appImManager.chatInputC.messageInput;
|
||||
const input = appImManager.chat.input.messageInput;
|
||||
if((input.lastChild as any)?.tagName) {
|
||||
input.lastElementChild.remove();
|
||||
} else if(input.lastChild) {
|
||||
@ -150,7 +151,7 @@ export class EmoticonsDropdown {
|
||||
}
|
||||
|
||||
const event = new Event('input', {bubbles: true, cancelable: true});
|
||||
appImManager.chatInputC.messageInput.dispatchEvent(event);
|
||||
appImManager.chat.input.messageInput.dispatchEvent(event);
|
||||
//appSidebarRight.stickersTab.init();
|
||||
});
|
||||
|
||||
@ -174,7 +175,7 @@ export class EmoticonsDropdown {
|
||||
};
|
||||
|
||||
public checkRights = () => {
|
||||
const peerID = rootScope.selectedPeerID;
|
||||
const peerID = appImManager.chat.peerID;
|
||||
const children = this.tabsEl.children;
|
||||
const tabsElements = Array.from(children) as HTMLElement[];
|
||||
|
||||
@ -206,17 +207,18 @@ export class EmoticonsDropdown {
|
||||
}
|
||||
|
||||
if(isTouchSupported) {
|
||||
this.toggleEl.classList.toggle('flip-icon', willBeActive);
|
||||
if(willBeActive) {
|
||||
appImManager.chatInputC.saveScroll();
|
||||
appImManager.chat.input.saveScroll();
|
||||
// @ts-ignore
|
||||
document.activeElement.blur();
|
||||
await new Promise((resolve) => {
|
||||
setTimeout(resolve, 100);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.toggleEl.classList.toggle('active', enable);
|
||||
}
|
||||
|
||||
if(this.element.parentElement !== appImManager.chat.input.chatInput) {
|
||||
appImManager.chat.input.chatInput.append(this.element);
|
||||
}
|
||||
|
||||
if((this.element.style.display && enable === undefined) || enable) {
|
||||
@ -349,7 +351,7 @@ export class EmoticonsDropdown {
|
||||
const fileID = target.dataset.docID;
|
||||
if(!fileID) return;
|
||||
|
||||
if(appImManager.chatInputC.sendMessageWithDocument(fileID)) {
|
||||
if(appImManager.chat.input.sendMessageWithDocument(fileID)) {
|
||||
/* dropdown.classList.remove('active');
|
||||
toggleEl.classList.remove('active'); */
|
||||
emoticonsDropdown.toggle(false);
|
||||
|
@ -187,7 +187,7 @@ export default class EmojiTab implements EmoticonsTab {
|
||||
|
||||
//console.log('contentEmoji div', target);
|
||||
|
||||
appImManager.chatInputC.messageInput.innerHTML += target.outerHTML;
|
||||
appImManager.chat.input.messageInput.innerHTML += target.outerHTML;
|
||||
|
||||
// Recent
|
||||
const emoji = this.getEmojiFromElement(target);
|
||||
@ -210,7 +210,7 @@ export default class EmojiTab implements EmoticonsTab {
|
||||
|
||||
// Append to input
|
||||
const event = new Event('input', {bubbles: true, cancelable: true});
|
||||
appImManager.chatInputC.messageInput.dispatchEvent(event);
|
||||
appImManager.chat.input.messageInput.dispatchEvent(event);
|
||||
};
|
||||
|
||||
onClose() {
|
||||
|
@ -338,7 +338,7 @@ export default class StickersTab implements EmoticonsTab {
|
||||
}
|
||||
|
||||
pushRecentSticker(doc: MyDocument) {
|
||||
if(!this.recentDiv.parentElement) {
|
||||
if(!this.recentDiv?.parentElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,7 @@ let init = () => {
|
||||
const InputField = (options: {
|
||||
placeholder?: string,
|
||||
label?: string,
|
||||
name: string,
|
||||
name?: string,
|
||||
maxLength?: number,
|
||||
showLengthOn?: number,
|
||||
plainText?: true
|
||||
@ -57,8 +57,8 @@ const InputField = (options: {
|
||||
}
|
||||
|
||||
div.innerHTML = `
|
||||
<div id="input-${name}" ${placeholder ? `data-placeholder="${placeholder}"` : ''} contenteditable="true" class="input-field-input"></div>
|
||||
${label ? `<label for="input-${name}">${label}</label>` : ''}
|
||||
<div ${placeholder ? `data-placeholder="${placeholder}"` : ''} contenteditable="true" class="input-field-input"></div>
|
||||
${label ? `<label>${label}</label>` : ''}
|
||||
`;
|
||||
|
||||
const input = div.firstElementChild as HTMLElement;
|
||||
@ -86,8 +86,8 @@ const InputField = (options: {
|
||||
observer.observe(input, {characterData: true, childList: true, subtree: true});
|
||||
} else {
|
||||
div.innerHTML = `
|
||||
<input type="text" name="${name}" id="input-${name}" ${placeholder ? `placeholder="${placeholder}"` : ''} autocomplete="off" required="" class="input-field-input">
|
||||
${label ? `<label for="input-${name}">${label}</label>` : ''}
|
||||
<input type="text" name="${name}" ${placeholder ? `placeholder="${placeholder}"` : ''} autocomplete="off" required="" class="input-field-input">
|
||||
${label ? `<label>${label}</label>` : ''}
|
||||
`;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import Countries, { Country, PhoneCodesMain } from "../countries";
|
||||
import { cancelEvent, CLICK_EVENT_NAME } from "../helpers/dom";
|
||||
import ListenerSetter from "../helpers/listenerSetter";
|
||||
import mediaSizes from "../helpers/mediaSizes";
|
||||
import { clamp } from "../helpers/number";
|
||||
import { isTouchSupported } from "../helpers/touchSupport";
|
||||
@ -295,7 +296,10 @@ export function positionMenu({pageX, pageY}: MouseEvent | Touch, elem: HTMLEleme
|
||||
(side == 'center' ? side : (side == 'left' ? 'right' : 'left')));
|
||||
}
|
||||
|
||||
export function attachContextMenuListener(element: HTMLElement, callback: (e: Touch | MouseEvent) => void) {
|
||||
export function attachContextMenuListener(element: HTMLElement, callback: (e: Touch | MouseEvent) => void, listenerSetter?: ListenerSetter) {
|
||||
const add = listenerSetter ? listenerSetter.add.bind(listenerSetter, element) : element.addEventListener.bind(element);
|
||||
const remove = listenerSetter ? listenerSetter.removeManual.bind(listenerSetter, element) : element.removeEventListener.bind(element);
|
||||
|
||||
if(isApple && isTouchSupported) {
|
||||
let timeout: number;
|
||||
|
||||
@ -303,20 +307,20 @@ export function attachContextMenuListener(element: HTMLElement, callback: (e: To
|
||||
|
||||
const onCancel = () => {
|
||||
clearTimeout(timeout);
|
||||
element.removeEventListener('touchmove', onCancel, options);
|
||||
element.removeEventListener('touchend', onCancel, options);
|
||||
element.removeEventListener('touchcancel', onCancel, options);
|
||||
remove('touchmove', onCancel, options);
|
||||
remove('touchend', onCancel, options);
|
||||
remove('touchcancel', onCancel, options);
|
||||
};
|
||||
|
||||
element.addEventListener('touchstart', (e) => {
|
||||
add('touchstart', (e: TouchEvent) => {
|
||||
if(e.touches.length > 1) {
|
||||
onCancel();
|
||||
return;
|
||||
}
|
||||
|
||||
element.addEventListener('touchmove', onCancel, options);
|
||||
element.addEventListener('touchend', onCancel, options);
|
||||
element.addEventListener('touchcancel', onCancel, options);
|
||||
add('touchmove', onCancel, options);
|
||||
add('touchend', onCancel, options);
|
||||
add('touchcancel', onCancel, options);
|
||||
|
||||
timeout = window.setTimeout(() => {
|
||||
element.addEventListener('touchend', cancelEvent, {once: true}); // * fix instant closing
|
||||
@ -325,6 +329,6 @@ export function attachContextMenuListener(element: HTMLElement, callback: (e: To
|
||||
}, .4e3);
|
||||
});
|
||||
} else {
|
||||
element.addEventListener('contextmenu', callback);
|
||||
add('contextmenu', callback);
|
||||
}
|
||||
};
|
||||
|
@ -118,7 +118,7 @@ const setQuizHint = (solution: string, solution_entities: any[], onHide: () => v
|
||||
element.append(container);
|
||||
|
||||
textEl.innerHTML = RichTextProcessor.wrapRichText(solution, {entities: solution_entities});
|
||||
appImManager.bubblesContainer.append(element);
|
||||
appImManager.chat.bubbles.bubblesContainer.append(element);
|
||||
|
||||
void element.offsetLeft; // reflow
|
||||
element.classList.add('active');
|
||||
|
@ -1,7 +1,6 @@
|
||||
import appMessagesManager from "../lib/appManagers/appMessagesManager";
|
||||
import appPeersManager from "../lib/appManagers/appPeersManager";
|
||||
import appPollsManager, { Poll } from "../lib/appManagers/appPollsManager";
|
||||
import rootScope from "../lib/rootScope";
|
||||
import { cancelEvent, findUpTag, getRichValue, isInputEmpty, whichChild } from "../helpers/dom";
|
||||
import CheckboxField from "./checkbox";
|
||||
import InputField from "./inputField";
|
||||
@ -27,7 +26,7 @@ export default class PopupCreatePoll extends PopupElement {
|
||||
private correctAnswers: Uint8Array[];
|
||||
private quizSolutionInput: HTMLInputElement;
|
||||
|
||||
constructor() {
|
||||
constructor(private peerID: number) {
|
||||
super('popup-create-poll popup-new-media', null, {closable: true, withConfirm: 'CREATE', body: true});
|
||||
|
||||
this.title.innerText = 'New Poll';
|
||||
@ -57,8 +56,6 @@ export default class PopupCreatePoll extends PopupElement {
|
||||
settingsCaption.classList.add('caption');
|
||||
settingsCaption.innerText = 'Settings';
|
||||
|
||||
const peerID = rootScope.selectedPeerID;
|
||||
|
||||
if(!appPeersManager.isBroadcast(peerID)) {
|
||||
this.anonymousCheckboxField = CheckboxField('Anonymous Voting', 'anonymous');
|
||||
this.anonymousCheckboxField.input.checked = true;
|
||||
@ -213,7 +210,7 @@ export default class PopupCreatePoll extends PopupElement {
|
||||
|
||||
//console.log('Will try to create poll:', inputMediaPoll);
|
||||
|
||||
appMessagesManager.sendOther(rootScope.selectedPeerID, inputMediaPoll);
|
||||
appMessagesManager.sendOther(this.peerID, inputMediaPoll);
|
||||
};
|
||||
|
||||
onInput = (e: Event) => {
|
||||
|
@ -7,7 +7,7 @@ import PopupPeer from "./popupPeer";
|
||||
|
||||
export default class PopupDeleteMessages {
|
||||
constructor(mids: number[], onConfirm?: () => void) {
|
||||
const peerID = rootScope.selectedPeerID;
|
||||
const peerID = appMessagesManager.getMessage(mids[0]).peerID;
|
||||
const firstName = appPeersManager.getPeerTitle(peerID, false, true);
|
||||
|
||||
mids = mids.slice();
|
||||
|
@ -20,8 +20,8 @@ export default class PopupForward extends PopupElement {
|
||||
|
||||
await (onSelect ? onSelect() || Promise.resolve() : Promise.resolve());
|
||||
|
||||
appImManager.setPeer(peerID);
|
||||
appImManager.chatInputC.initMessagesForward(mids.slice());
|
||||
appImManager.setInnerPeer(peerID);
|
||||
appImManager.chat.input.initMessagesForward(mids.slice());
|
||||
}, ['dialogs', 'contacts'], () => {
|
||||
this.show();
|
||||
this.selector.checkForTriggers(); // ! due to zero height before mounting
|
||||
|
@ -2,7 +2,6 @@ import { isTouchSupported } from "../helpers/touchSupport";
|
||||
import appImManager from "../lib/appManagers/appImManager";
|
||||
import appMessagesManager from "../lib/appManagers/appMessagesManager";
|
||||
import { calcImageInBox, getRichValue } from "../helpers/dom";
|
||||
import { Layouter, RectPart } from "./groupedLayout";
|
||||
import InputField from "./inputField";
|
||||
import { PopupElement } from "./popup";
|
||||
import { ripple } from "./ripple";
|
||||
@ -90,8 +89,8 @@ export default class PopupNewMedia extends PopupElement {
|
||||
|
||||
//console.log('will send files with options:', willAttach);
|
||||
|
||||
const peerID = appImManager.peerID;
|
||||
const chatInputC = appImManager.chatInputC;
|
||||
const peerID = appImManager.chat.peerID;
|
||||
const chatInputC = appImManager.chat.input;
|
||||
|
||||
if(willAttach.sendFileDetails.length > 1 && willAttach.isMedia) {
|
||||
appMessagesManager.sendAlbum(peerID, willAttach.sendFileDetails.map(d => d.file), Object.assign({
|
||||
|
@ -85,7 +85,7 @@ export default class PopupStickers extends PopupElement {
|
||||
if(!target) return;
|
||||
|
||||
const fileID = target.dataset.docID;
|
||||
if(appImManager.chatInputC.sendMessageWithDocument(fileID)) {
|
||||
if(appImManager.chat.input.sendMessageWithDocument(fileID)) {
|
||||
this.closeBtn.click();
|
||||
} else {
|
||||
console.warn('got no doc by id:', fileID);
|
||||
|
@ -20,7 +20,7 @@ export default class AppIncludedChatsTab implements SliderTab {
|
||||
private originalFilter: DialogFilter;
|
||||
|
||||
init() {
|
||||
this.container = document.querySelector('.included-chats-container');
|
||||
this.container = document.querySelector('.included-chatlist-container');
|
||||
this.closeBtn = this.container.querySelector('.sidebar-close-button');
|
||||
this.confirmBtn = this.container.querySelector('.btn-confirm');
|
||||
this.title = this.container.querySelector('.sidebar-header__title');
|
||||
|
@ -46,7 +46,7 @@ export default class AppNewGroupTab implements SliderTab {
|
||||
});
|
||||
|
||||
const chatsContainer = document.createElement('div');
|
||||
chatsContainer.classList.add('chats-container');
|
||||
chatsContainer.classList.add('chatlist-container');
|
||||
chatsContainer.append(this.searchGroup.container);
|
||||
|
||||
const scrollable = new Scrollable(chatsContainer);
|
||||
|
@ -50,7 +50,7 @@ export default class AppGifsTab implements SliderTab {
|
||||
if(!target) return;
|
||||
|
||||
const fileID = target.dataset.docID;
|
||||
if(appImManager.chatInputC.sendMessageWithDocument(fileID)) {
|
||||
if(appImManager.chat.input.sendMessageWithDocument(fileID)) {
|
||||
if(mediaSizes.isMobile) {
|
||||
this.backBtn.click();
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ export default class AppPrivateSearchTab implements SliderTab {
|
||||
this.closeBtn = this.container.querySelector('.sidebar-close-button');
|
||||
this.searchInput = new SearchInput('Search');
|
||||
this.closeBtn.parentElement.append(this.searchInput.container);
|
||||
this.appSearch = new AppSearch(this.container.querySelector('.chats-container'), this.searchInput, {
|
||||
this.appSearch = new AppSearch(this.container.querySelector('.chatlist-container'), this.searchInput, {
|
||||
messages: new SearchGroup('Private Search', 'messages')
|
||||
});
|
||||
}
|
||||
|
@ -100,6 +100,7 @@ export default class AppSharedMediaTab implements SliderTab {
|
||||
private loadMutex: Promise<any> = Promise.resolve();
|
||||
|
||||
private log = logger('SM'/* , LogLevels.error */);
|
||||
setPeerStatusInterval: number;
|
||||
|
||||
public init() {
|
||||
this.container = document.getElementById('shared-media-container');
|
||||
@ -193,14 +194,57 @@ export default class AppSharedMediaTab implements SliderTab {
|
||||
|
||||
this.profileElements.notificationsCheckbox.addEventListener('change', () => {
|
||||
//let checked = this.profileElements.notificationsCheckbox.checked;
|
||||
appImManager.mutePeer(this.peerID);
|
||||
appMessagesManager.mutePeer(this.peerID);
|
||||
});
|
||||
|
||||
rootScope.on('dialog_notify_settings', (e) => {
|
||||
if(this.peerID == e.detail) {
|
||||
const muted = appMessagesManager.isPeerMuted(this.peerID);
|
||||
this.profileElements.notificationsCheckbox.checked = !muted;
|
||||
this.profileElements.notificationsStatus.innerText = muted ? 'Disabled' : 'Enabled';
|
||||
}
|
||||
});
|
||||
|
||||
rootScope.on('peer_typings', (e) => {
|
||||
const {peerID} = e.detail;
|
||||
|
||||
if(this.peerID == peerID) {
|
||||
this.setPeerStatus();
|
||||
}
|
||||
});
|
||||
|
||||
rootScope.on('user_update', (e) => {
|
||||
const userID = e.detail;
|
||||
|
||||
if(this.peerID == userID) {
|
||||
this.setPeerStatus();
|
||||
}
|
||||
});
|
||||
|
||||
this.setPeerStatusInterval = window.setInterval(this.setPeerStatus, 60e3);
|
||||
|
||||
/* this.closeBtn.addEventListener('click', () => {
|
||||
this.toggleSidebar(false);
|
||||
}); */
|
||||
}
|
||||
|
||||
public setPeerStatus = (needClear = false) => {
|
||||
if(!this.peerID) return;
|
||||
|
||||
const peerID = this.peerID;
|
||||
if(needClear) {
|
||||
this.profileElements.subtitle.innerHTML = '';
|
||||
}
|
||||
|
||||
appImManager.getPeerStatus(this.peerID).then((subtitle) => {
|
||||
if(peerID != this.peerID) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.profileElements.subtitle.innerHTML = subtitle;
|
||||
});
|
||||
};
|
||||
|
||||
public renderNewMessages(peerID: number, mids: number[]) {
|
||||
if(this.init) return; // * not inited yet
|
||||
|
||||
@ -408,7 +452,7 @@ export default class AppSharedMediaTab implements SliderTab {
|
||||
|
||||
const load = () => appPhotosManager.preloadPhoto(isPhoto ? media.id : media, appPhotosManager.choosePhotoSize(media, 200, 200))
|
||||
.then(() => {
|
||||
if(rootScope.selectedPeerID != peerID) {
|
||||
if(appImManager.chat.peerID != peerID) {
|
||||
this.log.warn('peer changed');
|
||||
return;
|
||||
}
|
||||
@ -552,7 +596,7 @@ export default class AppSharedMediaTab implements SliderTab {
|
||||
if(webpage.photo) {
|
||||
let load = () => appPhotosManager.preloadPhoto(webpage.photo.id, appPhotosManager.choosePhotoSize(webpage.photo, 60, 60))
|
||||
.then(() => {
|
||||
if(rootScope.selectedPeerID != peerID) {
|
||||
if(appImManager.chat.peerID != peerID) {
|
||||
this.log.warn('peer changed');
|
||||
return;
|
||||
}
|
||||
@ -702,7 +746,7 @@ export default class AppSharedMediaTab implements SliderTab {
|
||||
|
||||
this.log(logStr + 'search house of glass', type, value);
|
||||
|
||||
if(rootScope.selectedPeerID != peerID) {
|
||||
if(appImManager.chat.peerID != peerID) {
|
||||
this.log.warn('peer changed');
|
||||
return;
|
||||
}
|
||||
@ -827,7 +871,7 @@ export default class AppSharedMediaTab implements SliderTab {
|
||||
}
|
||||
|
||||
public fillProfileElements() {
|
||||
let peerID = this.peerID = rootScope.selectedPeerID;
|
||||
let peerID = this.peerID = appImManager.chat.peerID;
|
||||
|
||||
this.cleanupHTML();
|
||||
|
||||
@ -846,8 +890,6 @@ export default class AppSharedMediaTab implements SliderTab {
|
||||
if(dialog.notify_settings && dialog.notify_settings.mute_until) {
|
||||
muted = new Date(dialog.notify_settings.mute_until * 1000) > new Date();
|
||||
}
|
||||
|
||||
appImManager.setMutedState(muted);
|
||||
}
|
||||
} else {
|
||||
window.requestAnimationFrame(() => {
|
||||
@ -893,6 +935,13 @@ export default class AppSharedMediaTab implements SliderTab {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let title: string;
|
||||
if(peerID == rootScope.myID) title = 'Saved Messages';
|
||||
else title = appPeersManager.getPeerTitle(peerID);
|
||||
this.profileElements.name.innerHTML = title;
|
||||
|
||||
this.setPeerStatus(true);
|
||||
}
|
||||
|
||||
/* onOpen() {
|
||||
|
@ -37,7 +37,7 @@ export default class AppStickersTab implements SliderTab {
|
||||
const sticker = findUpClassName(e.target, 'sticker-set-sticker');
|
||||
if(sticker) {
|
||||
const docID = sticker.dataset.docID;
|
||||
appImManager.chatInputC.sendMessageWithDocument(docID);
|
||||
appImManager.chat.input.sendMessageWithDocument(docID);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { whichChild } from "../helpers/dom";
|
||||
|
||||
function slideNavigation(tabContent: HTMLElement, prevTabContent: HTMLElement, toRight: boolean) {
|
||||
const width = prevTabContent.getBoundingClientRect().width;
|
||||
const elements = [tabContent, prevTabContent];
|
||||
@ -79,8 +81,13 @@ const Transition = (content: HTMLElement, animationFunction: TransitionFunction,
|
||||
let transitionEndTimeout: number;
|
||||
let prevTabContent: HTMLElement = null;
|
||||
|
||||
function selectTab(id: number, animate = true) {
|
||||
function selectTab(id: number | HTMLElement, animate = true) {
|
||||
const self = selectTab;
|
||||
|
||||
if(id instanceof HTMLElement) {
|
||||
id = whichChild(id);
|
||||
}
|
||||
|
||||
if(id == self.prevId) return false;
|
||||
|
||||
//console.log('selectTab id:', id);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { MOUNT_CLASS_TO } from "../lib/mtproto/mtproto_config";
|
||||
import ListenerSetter from "./listenerSetter";
|
||||
import { isTouchSupported } from "./touchSupport";
|
||||
import { isSafari } from "./userAgent";
|
||||
|
||||
@ -464,30 +465,34 @@ export function blurActiveElement() {
|
||||
}
|
||||
|
||||
export const CLICK_EVENT_NAME = isTouchSupported ? 'touchend' : 'click';
|
||||
export const attachClickEvent = (elem: HTMLElement, callback: (e: TouchEvent | MouseEvent) => void, options: AddEventListenerOptions = {}) => {
|
||||
export type AttachClickOptions = AddEventListenerOptions & Partial<{listenerSetter: ListenerSetter}>;
|
||||
export const attachClickEvent = (elem: HTMLElement, callback: (e: TouchEvent | MouseEvent) => void, options: AttachClickOptions = {}) => {
|
||||
const add = options.listenerSetter ? options.listenerSetter.add.bind(options.listenerSetter, elem) : elem.addEventListener.bind(elem);
|
||||
const remove = options.listenerSetter ? options.listenerSetter.removeManual.bind(options.listenerSetter, elem) : elem.removeEventListener.bind(elem);
|
||||
|
||||
if(CLICK_EVENT_NAME == 'touchend') {
|
||||
const o = {...options, once: true};
|
||||
|
||||
const onTouchStart = (e: TouchEvent) => {
|
||||
const onTouchMove = (e: TouchEvent) => {
|
||||
elem.removeEventListener('touchend', onTouchEnd, o);
|
||||
remove('touchend', onTouchEnd, o);
|
||||
};
|
||||
|
||||
const onTouchEnd = (e: TouchEvent) => {
|
||||
elem.removeEventListener('touchmove', onTouchMove, o);
|
||||
remove('touchmove', onTouchMove, o);
|
||||
callback(e);
|
||||
if(options.once) {
|
||||
elem.removeEventListener('touchstart', onTouchStart);
|
||||
remove('touchstart', onTouchStart);
|
||||
}
|
||||
};
|
||||
|
||||
elem.addEventListener('touchend', onTouchEnd, o);
|
||||
elem.addEventListener('touchmove', onTouchMove, o);
|
||||
add('touchend', onTouchEnd, o);
|
||||
add('touchmove', onTouchMove, o);
|
||||
};
|
||||
|
||||
elem.addEventListener('touchstart', onTouchStart);
|
||||
add('touchstart', onTouchStart);
|
||||
} else {
|
||||
elem.addEventListener(CLICK_EVENT_NAME, callback, options);
|
||||
add(CLICK_EVENT_NAME, callback, options);
|
||||
}
|
||||
};
|
||||
|
||||
|
44
src/helpers/listenerSetter.ts
Normal file
44
src/helpers/listenerSetter.ts
Normal file
@ -0,0 +1,44 @@
|
||||
export type Listener = {element: ListenerElement, event: ListenerEvent, callback: ListenerCallback, options?: ListenerOptions};
|
||||
export type ListenerElement = any;
|
||||
export type ListenerEvent = string;
|
||||
export type ListenerOptions = any;
|
||||
export type ListenerCallback = (...args: any[]) => any;
|
||||
export default class ListenerSetter {
|
||||
private listeners: Set<Listener> = new Set();
|
||||
|
||||
public add = (element: ListenerElement, event: ListenerEvent, callback: ListenerCallback, options?: ListenerOptions) => {
|
||||
const listener = {element, event, callback, options};
|
||||
this.addManual(listener);
|
||||
return listener;
|
||||
};
|
||||
|
||||
public addManual = (listener: Listener) => {
|
||||
listener.element.addEventListener(listener.event, listener.callback, listener.options);
|
||||
this.listeners.add(listener);
|
||||
};
|
||||
|
||||
public remove = (listener: Listener) => {
|
||||
listener.element.removeEventListener(listener.event, listener.callback, listener.options);
|
||||
this.listeners.delete(listener);
|
||||
};
|
||||
|
||||
public removeManual = (element: ListenerElement, event: ListenerEvent, callback: ListenerCallback, options?: ListenerOptions) => {
|
||||
let listener: Listener;
|
||||
for(const _listener of this.listeners) {
|
||||
if(_listener.element === element && _listener.event === event && _listener.callback === callback && _listener.options === options) {
|
||||
listener = _listener;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(listener) {
|
||||
this.remove(listener);
|
||||
}
|
||||
};
|
||||
|
||||
public removeAll = () => {
|
||||
this.listeners.forEach(listener => {
|
||||
this.remove(listener);
|
||||
});
|
||||
};
|
||||
}
|
176
src/index.hbs
176
src/index.hbs
@ -134,7 +134,7 @@
|
||||
</defs>
|
||||
</svg>
|
||||
<div id="main-columns" class="tabs-container">
|
||||
<div class="chats-container sidebar sidebar-left main-column" id="column-left">
|
||||
<div class="chatlist-container sidebar sidebar-left main-column" id="column-left">
|
||||
<div class="sidebar-slider tabs-container">
|
||||
<div class="sidebar-slider-item item-main">
|
||||
<div class="sidebar-header">
|
||||
@ -153,7 +153,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-content transition zoom-fade">
|
||||
<div class="transition-item active" id="chats-container">
|
||||
<div class="transition-item active" id="chatlist-container">
|
||||
<div class="folders-tabs-scrollable hide">
|
||||
<nav class="menu-horizontal" id="folders-tabs">
|
||||
<ul>
|
||||
@ -365,7 +365,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-slider-item included-chats-container">
|
||||
<div class="sidebar-slider-item included-chatlist-container">
|
||||
<div class="sidebar-header">
|
||||
<button class="btn-icon tgico-back sidebar-close-button"></button>
|
||||
<div class="sidebar-header__title"></div>
|
||||
@ -374,132 +374,14 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-container main-column" id="column-center">
|
||||
{{!-- <canvas id="chat-background-canvas"></canvas> --}}
|
||||
<div class="chat-background"></div>
|
||||
<div id="topbar" style="display: none;" class="sidebar-header">
|
||||
<button class="btn-icon tgico-back sidebar-close-button"></button>
|
||||
<div class="chat-info">
|
||||
<div class="person">
|
||||
<avatar-element id="im-avatar" dialog="1" clickable></avatar-element>
|
||||
<div class="content">
|
||||
<div class="top">
|
||||
<div class="user-title" id="im-title"></div>
|
||||
</div>
|
||||
<div class="bottom">
|
||||
<div class="info" id="im-subtitle"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-utils">
|
||||
<button class="btn-primary rp chat-join hide">SUBSCRIBE</button>
|
||||
<div class="btn-icon rp chat-mute-button hide"></div>
|
||||
<div class="btn-icon rp tgico-search chat-search-button"></div>
|
||||
<div class="btn-icon btn-menu-toggle rp tgico-more chat-more-button">
|
||||
<div class="btn-menu bottom-left">
|
||||
<div class="btn-menu-item menu-search tgico-search rp">Search</div>
|
||||
<div class="btn-menu-item menu-mute rp">Mute</div>
|
||||
<div class="btn-menu-item menu-delete tgico-delete danger rp btn-disabled">Delete and Leave</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="bubbles" class="scrolled-down">
|
||||
{{!-- <div id="bubbles-transform-helper"> --}}
|
||||
<div id="bubbles-inner"></div>
|
||||
<div id="bubbles-go-down" class="tgico-down btn-corner z-depth-1 rp hide"></div>
|
||||
{{!-- </div> --}}
|
||||
</div>
|
||||
<div id="chat-input" style="display: none;">
|
||||
<div class="chat-input-container">
|
||||
<div class="input-message">
|
||||
<div class="reply-wrapper">
|
||||
<button class="btn-icon rp tgico-close reply-cancel"></button>
|
||||
<div class="reply">
|
||||
<div class="reply-border"></div>
|
||||
<div class="reply-content">
|
||||
<div class="reply-title"></div>
|
||||
<div class="reply-subtitle"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="new-message-wrapper">
|
||||
<button class="btn-icon rp tgico toggle-emoticons" id="toggle-emoticons"></button>
|
||||
<!-- <textarea type="text" id="input-message" placeholder="Message" contenteditable="true"></textarea> -->
|
||||
<div class="input-message-container"></div>
|
||||
<button class="btn-icon tgico-attach btn-menu-toggle" id="attach-file"></button>
|
||||
<div class="record-time"></div>
|
||||
<input type="file" id="input-file" style="display: none;" multiple />
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-circle z-depth-1 btn-icon tgico-delete danger" id="btn-record-cancel"></button>
|
||||
<div class="btn-send-container">
|
||||
<div class="record-ripple"></div>
|
||||
<button class="btn-circle rp z-depth-1 btn-icon" id="btn-send">
|
||||
<span class="tgico tgico-send"></span>
|
||||
<span class="tgico tgico-microphone2"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="emoji-dropdown" id="emoji-dropdown" style="display: none;">
|
||||
<div class="emoji-container">
|
||||
<div class="tabs-container">
|
||||
<div class="emoji-padding">
|
||||
<nav class="menu-horizontal-div no-stripe">
|
||||
<button class="menu-horizontal-div-item active btn-icon tgico-recent rp"></button>
|
||||
<button class="menu-horizontal-div-item btn-icon tgico-smile rp"></button>
|
||||
<button class="menu-horizontal-div-item btn-icon tgico-animals rp"></button>
|
||||
<button class="menu-horizontal-div-item btn-icon tgico-eats rp"></button>
|
||||
<button class="menu-horizontal-div-item btn-icon tgico-car rp"></button>
|
||||
<button class="menu-horizontal-div-item btn-icon tgico-sport rp"></button>
|
||||
<button class="menu-horizontal-div-item btn-icon tgico-lamp rp"></button>
|
||||
<!-- <button class="menu-horizontal-div-item btn-icon tgico-info rp"></button> -->
|
||||
<button class="menu-horizontal-div-item btn-icon tgico-flag rp"></button>
|
||||
</nav>
|
||||
<div class="emoticons-content" id="content-emoji"></div>
|
||||
</div>
|
||||
<div class="stickers-padding">
|
||||
<div class="menu-wrapper">
|
||||
<nav class="menu-horizontal-div no-stripe justify-start">
|
||||
<button class="menu-horizontal-div-item btn-icon tgico-recent active"></button>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="emoticons-content" id="content-stickers"></div>
|
||||
</div>
|
||||
<div class="gifs-padding">
|
||||
<div class="emoticons-content" id="content-gifs">
|
||||
<div class="gifs-masonry"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="emoji-tabs menu-horizontal-div no-stripe">
|
||||
<button class="menu-horizontal-div-item emoji-tabs-search justify-self-start btn-icon tgico-search rp" data-tab="-1"></button>
|
||||
<button class="menu-horizontal-div-item emoji-tabs-emoji btn-icon tgico-smile rp" data-tab="0"></button>
|
||||
<button class="menu-horizontal-div-item emoji-tabs-stickers btn-icon tgico-stickers rp" data-tab="1"></button>
|
||||
<button class="menu-horizontal-div-item emoji-tabs-gifs btn-icon tgico-gifs rp" data-tab="2"></button>
|
||||
<button class="menu-horizontal-div-item emoji-tabs-delete justify-self-end btn-icon tgico-deleteleft rp" data-tab="-1"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main-column" id="column-center"></div>
|
||||
<div class="sidebar sidebar-right main-column" id="column-right">
|
||||
<div class="sidebar-content sidebar-slider tabs-container">
|
||||
<div class="sidebar-slider-item profile-container" id="shared-media-container">
|
||||
<div class="sidebar-header">
|
||||
<button class="btn-icon tgico sidebar-close-button"></button>
|
||||
<div class="sidebar-header__title">Info</div>
|
||||
|
||||
<!-- <button class="btn-icon rp tgico-edit sidebar-edit-button"></button> -->
|
||||
|
||||
<div class="btn-icon tgico-more"></div>
|
||||
<!-- <div class="btn-icon tgico-more rp btn-menu-toggle">
|
||||
<div class="btn-menu bottom-left"> -->
|
||||
<!-- <div class="btn-menu-item menu-mute rp">Mute</div>
|
||||
<div class="btn-menu-item menu-delete tgico-delete danger rp">Delete and Leave</div> -->
|
||||
<!-- </div>
|
||||
</div> -->
|
||||
</div>
|
||||
<div class="profile-content">
|
||||
<div class="profile-content-wrapper">
|
||||
@ -547,26 +429,26 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sidebar-slider-item chats-container" id="search-private-container">
|
||||
<div class="sidebar-slider-item chatlist-container" id="search-private-container">
|
||||
<div class="sidebar-header">
|
||||
<button class="btn-icon tgico-close sidebar-close-button"></button>
|
||||
</div>
|
||||
<div class="chats-container"></div>
|
||||
<div class="chatlist-container"></div>
|
||||
</div>
|
||||
<div class="sidebar-slider-item chats-container" id="stickers-container">
|
||||
<div class="sidebar-slider-item chatlist-container" id="stickers-container">
|
||||
<div class="sidebar-header">
|
||||
<button class="btn-icon tgico-close sidebar-close-button"></button>
|
||||
</div>
|
||||
<div class="sidebar-content"><div class="sticker-sets"></div></div>
|
||||
</div>
|
||||
<div class="sidebar-slider-item chats-container" id="poll-results-container">
|
||||
<div class="sidebar-slider-item chatlist-container" id="poll-results-container">
|
||||
<div class="sidebar-header">
|
||||
<button class="btn-icon tgico-close sidebar-close-button"></button>
|
||||
<div class="sidebar-header__title">Results</div>
|
||||
</div>
|
||||
<div class="sidebar-content"><div class="poll-results"></div></div>
|
||||
</div>
|
||||
<div class="sidebar-slider-item chats-container" id="search-gifs-container">
|
||||
<div class="sidebar-slider-item chatlist-container" id="search-gifs-container">
|
||||
<div class="sidebar-header">
|
||||
<button class="btn-icon tgico-close sidebar-close-button"></button>
|
||||
</div>
|
||||
@ -582,6 +464,46 @@
|
||||
<div class="btn-menu-item menu-archive tgico rp"><div></div></div>
|
||||
<div class="btn-menu-item menu-delete tgico-delete danger rp"><div></div></div>
|
||||
</div>
|
||||
<div class="emoji-dropdown" id="emoji-dropdown" style="display: none;">
|
||||
<div class="emoji-container">
|
||||
<div class="tabs-container">
|
||||
<div class="emoji-padding">
|
||||
<nav class="menu-horizontal-div no-stripe">
|
||||
<button class="menu-horizontal-div-item active btn-icon tgico-recent rp"></button>
|
||||
<button class="menu-horizontal-div-item btn-icon tgico-smile rp"></button>
|
||||
<button class="menu-horizontal-div-item btn-icon tgico-animals rp"></button>
|
||||
<button class="menu-horizontal-div-item btn-icon tgico-eats rp"></button>
|
||||
<button class="menu-horizontal-div-item btn-icon tgico-car rp"></button>
|
||||
<button class="menu-horizontal-div-item btn-icon tgico-sport rp"></button>
|
||||
<button class="menu-horizontal-div-item btn-icon tgico-lamp rp"></button>
|
||||
<!-- <button class="menu-horizontal-div-item btn-icon tgico-info rp"></button> -->
|
||||
<button class="menu-horizontal-div-item btn-icon tgico-flag rp"></button>
|
||||
</nav>
|
||||
<div class="emoticons-content" id="content-emoji"></div>
|
||||
</div>
|
||||
<div class="stickers-padding">
|
||||
<div class="menu-wrapper">
|
||||
<nav class="menu-horizontal-div no-stripe justify-start">
|
||||
<button class="menu-horizontal-div-item btn-icon tgico-recent active"></button>
|
||||
</nav>
|
||||
</div>
|
||||
<div class="emoticons-content" id="content-stickers"></div>
|
||||
</div>
|
||||
<div class="gifs-padding">
|
||||
<div class="emoticons-content" id="content-gifs">
|
||||
<div class="gifs-masonry"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="emoji-tabs menu-horizontal-div no-stripe">
|
||||
<button class="menu-horizontal-div-item emoji-tabs-search justify-self-start btn-icon tgico-search rp" data-tab="-1"></button>
|
||||
<button class="menu-horizontal-div-item emoji-tabs-emoji btn-icon tgico-smile rp" data-tab="0"></button>
|
||||
<button class="menu-horizontal-div-item emoji-tabs-stickers btn-icon tgico-stickers rp" data-tab="1"></button>
|
||||
<button class="menu-horizontal-div-item emoji-tabs-gifs btn-icon tgico-gifs rp" data-tab="2"></button>
|
||||
<button class="menu-horizontal-div-item emoji-tabs-delete justify-self-end btn-icon tgico-deleteleft rp" data-tab="-1"></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{# each htmlWebpackPlugin.files.js }}
|
||||
<script src="{{ this }}"></script>
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { numberWithCommas } from "../../helpers/number";
|
||||
import { isObject, safeReplaceObject, copy } from "../../helpers/object";
|
||||
import { ChatAdminRights, ChatBannedRights, ChatFull, ChatParticipants, InputChannel, InputChatPhoto, InputFile, InputPeer, Updates } from "../../layer";
|
||||
import { ChatAdminRights, ChatBannedRights, ChatFull, ChatParticipants, InputChannel, InputChatPhoto, InputFile, InputPeer, SendMessageAction, Updates } from "../../layer";
|
||||
import { addFunction } from "../../rlottie.github.io/a.out";
|
||||
import apiManager from '../mtproto/mtprotoworker';
|
||||
import { MOUNT_CLASS_TO } from "../mtproto/mtproto_config";
|
||||
import { RichTextProcessor } from "../richtextprocessor";
|
||||
@ -64,6 +65,8 @@ export type Chat = {
|
||||
|
||||
export type ChatRights = 'send' | 'edit_title' | 'edit_photo' | 'invite' | 'pin' | 'deleteRevoke' | 'delete';
|
||||
|
||||
export type UserTyping = Partial<{userID: number, action: SendMessageAction, timeout: number}>;
|
||||
|
||||
export class AppChatsManager {
|
||||
public chats: {[id: number]: Channel | Chat | any} = {};
|
||||
//public usernames: any = {};
|
||||
@ -73,6 +76,8 @@ export class AppChatsManager {
|
||||
|
||||
public megagroupOnlines: {[id: number]: {timestamp: number, onlines: number}} = {};
|
||||
|
||||
public typingsInPeer: {[peerID: number]: UserTyping[]} = {};
|
||||
|
||||
constructor() {
|
||||
rootScope.on('apiUpdate', (e) => {
|
||||
// console.log('on apiUpdate', update)
|
||||
@ -83,6 +88,56 @@ export class AppChatsManager {
|
||||
//console.log('updateChannel:', update);
|
||||
rootScope.broadcast('channel_settings', {channelID: channelID});
|
||||
break;
|
||||
|
||||
case 'updateUserTyping':
|
||||
case 'updateChatUserTyping': {
|
||||
if(rootScope.myID == update.user_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const peerID = update._ == 'updateUserTyping' ? update.user_id : -update.chat_id;
|
||||
const typings = this.typingsInPeer[peerID] ?? (this.typingsInPeer[peerID] = []);
|
||||
let typing = typings.find(t => t.userID == update.user_id);
|
||||
if(!typing) {
|
||||
typing = {
|
||||
userID: update.user_id
|
||||
};
|
||||
|
||||
typings.push(typing);
|
||||
}
|
||||
|
||||
//console.log('updateChatUserTyping', update, typings);
|
||||
|
||||
typing.action = update.action;
|
||||
|
||||
if(!appUsersManager.hasUser(update.user_id)) {
|
||||
if(update._ == 'updateChatUserTyping') {
|
||||
if(update.chat_id && appChatsManager.hasChat(update.chat_id) && !appChatsManager.isChannel(update.chat_id)) {
|
||||
appProfileManager.getChatFull(update.chat_id);
|
||||
}
|
||||
}
|
||||
|
||||
//return;
|
||||
}
|
||||
|
||||
appUsersManager.forceUserOnline(update.user_id);
|
||||
|
||||
if(typing.timeout !== undefined) clearTimeout(typing.timeout);
|
||||
|
||||
typing.timeout = window.setTimeout(() => {
|
||||
delete typing.timeout;
|
||||
typings.findAndSplice(t => t.userID == update.user_id);
|
||||
|
||||
rootScope.broadcast('peer_typings', {peerID, typings});
|
||||
|
||||
if(!typings.length) {
|
||||
delete this.typingsInPeer[peerID];
|
||||
}
|
||||
}, 6000);
|
||||
|
||||
rootScope.broadcast('peer_typings', {peerID, typings});
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -160,11 +160,7 @@ export class AppDialogsManager {
|
||||
public doms: {[peerID: number]: DialogDom} = {};
|
||||
public lastActiveListElement: HTMLElement = null;
|
||||
|
||||
/* private rippleCallback: (value?: boolean | PromiseLike<boolean>) => void = null;
|
||||
private lastClickID = 0;
|
||||
private lastGoodClickID = 0; */
|
||||
|
||||
public chatsContainer = document.getElementById('chats-container') as HTMLDivElement;
|
||||
public chatsContainer = document.getElementById('chatlist-container') as HTMLDivElement;
|
||||
private chatsPreloader: HTMLDivElement;
|
||||
|
||||
public loadDialogsPromise: Promise<any>;
|
||||
@ -276,10 +272,6 @@ export class AppDialogsManager {
|
||||
dom.avatarEl.classList.toggle('is-online', online);
|
||||
}
|
||||
}
|
||||
|
||||
if(rootScope.selectedPeerID == user.id) {
|
||||
appImManager.setPeerStatus();
|
||||
}
|
||||
});
|
||||
|
||||
/* rootScope.$on('dialog_top', (e) => {
|
||||
@ -331,11 +323,6 @@ export class AppDialogsManager {
|
||||
const dialog = appMessagesManager.getDialogByPeerID(info.peerID)[0];
|
||||
if(dialog) {
|
||||
this.setUnreadMessages(dialog);
|
||||
|
||||
if(dialog.peerID == rootScope.selectedPeerID) {
|
||||
appImManager.updateUnreadByDialog(dialog);
|
||||
}
|
||||
|
||||
this.validateForFilter();
|
||||
this.setFiltersUnreadCount();
|
||||
}
|
||||
@ -424,6 +411,19 @@ export class AppDialogsManager {
|
||||
}
|
||||
});
|
||||
|
||||
rootScope.on('peer_typings', (e) => {
|
||||
const {peerID, typings} = e.detail;
|
||||
|
||||
const dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
|
||||
if(!dialog) return;
|
||||
|
||||
if(typings.length) {
|
||||
this.setTyping(dialog, appUsersManager.getUser(typings[0]));
|
||||
} else {
|
||||
this.unsetTyping(dialog);
|
||||
}
|
||||
});
|
||||
|
||||
const foldersScrollable = new ScrollableX(this.folders.menuScrollContainer);
|
||||
bottomPart.prepend(this.folders.menuScrollContainer);
|
||||
const selectTab = horizontalMenu(this.folders.menu, this.folders.container, (id, tabContent) => {
|
||||
@ -861,8 +861,6 @@ export class AppDialogsManager {
|
||||
this.lastActiveListElement.classList.remove('active');
|
||||
}
|
||||
|
||||
let result: ReturnType<AppImManager['setPeer']>;
|
||||
//console.log('appDialogsManager: lock lazyLoadQueue');
|
||||
if(elem) {
|
||||
if(onFound) onFound();
|
||||
|
||||
@ -874,14 +872,9 @@ export class AppDialogsManager {
|
||||
this.lastActiveListElement = elem;
|
||||
}
|
||||
|
||||
result = appImManager.setPeer(peerID, lastMsgID);
|
||||
|
||||
/* if(result instanceof Promise) {
|
||||
this.lastGoodClickID = this.lastClickID;
|
||||
appImManager.lazyLoadQueue.lock();
|
||||
} */
|
||||
appImManager.setPeer(peerID, lastMsgID);
|
||||
} else {
|
||||
result = appImManager.setPeer(0);
|
||||
appImManager.setPeer(0);
|
||||
}
|
||||
}, {capture: true});
|
||||
|
||||
@ -1217,23 +1210,7 @@ export class AppDialogsManager {
|
||||
|
||||
if(rippleEnabled) {
|
||||
ripple(paddingDiv);
|
||||
/* ripple(paddingDiv, (id) => {
|
||||
this.log('dialogs click element');
|
||||
this.lastClickID = id;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.rippleCallback = resolve;
|
||||
//setTimeout(() => resolve(), 100);
|
||||
//window.requestAnimationFrame(() => window.requestAnimationFrame(() => resolve()));
|
||||
});
|
||||
}, (id) => {
|
||||
//console.log('appDialogsManager: ripple onEnd called!');
|
||||
if(id == this.lastGoodClickID) {
|
||||
appImManager.lazyLoadQueue.unlock();
|
||||
}
|
||||
}); */
|
||||
}
|
||||
|
||||
|
||||
const li = document.createElement('li');
|
||||
li.append(paddingDiv);
|
||||
@ -1287,7 +1264,7 @@ export class AppDialogsManager {
|
||||
|
||||
this.doms[dialog.peerID] = dom;
|
||||
|
||||
if(rootScope.selectedPeerID == peerID) {
|
||||
if(appImManager.chat?.peerID == peerID) {
|
||||
li.classList.add('active');
|
||||
this.lastActiveListElement = li;
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ export type MyDocument = Document.document;
|
||||
|
||||
// TODO: если залить картинку файлом, а потом перезайти в диалог - превьюшка заново скачается
|
||||
|
||||
class AppDocsManager {
|
||||
export class AppDocsManager {
|
||||
private docs: {[docID: string]: MyDocument} = {};
|
||||
private savingLottiePreview: {[docID: string]: true} = {};
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -5,7 +5,7 @@ import { tsNow } from "../../helpers/date";
|
||||
import { copy, defineNotNumerableProperties, deepEqual, safeReplaceObject, getObjectKeysAndSort } from "../../helpers/object";
|
||||
import { randomLong } from "../../helpers/random";
|
||||
import { splitStringByLength, limitSymbols } from "../../helpers/string";
|
||||
import { Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMessage, Message, MessageAction, MessageEntity, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, PhotoSize, SendMessageAction, Update } from "../../layer";
|
||||
import { Dialog as MTDialog, DialogPeer, DocumentAttribute, InputMessage, InputNotifyPeer, InputPeerNotifySettings, Message, MessageAction, MessageEntity, MessagesDialogs, MessagesFilter, MessagesMessages, MessagesPeerDialogs, MethodDeclMap, NotifyPeer, PhotoSize, SendMessageAction, Update } from "../../layer";
|
||||
import { InvokeApiOptions } from "../../types";
|
||||
import { langPack } from "../langPack";
|
||||
import { logger, LogLevels } from "../logger";
|
||||
@ -3857,9 +3857,100 @@ export class AppMessagesManager {
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'updateNotifySettings': {
|
||||
const {peer, notify_settings} = update;
|
||||
|
||||
const peerID = appPeersManager.getPeerID((peer as NotifyPeer.notifyPeer).peer);
|
||||
|
||||
const dialog = this.getDialogByPeerID(peerID)[0];
|
||||
if(dialog) {
|
||||
dialog.notify_settings = notify_settings;
|
||||
rootScope.broadcast('dialog_notify_settings', peerID);
|
||||
}
|
||||
|
||||
/////this.log('updateNotifySettings', peerID, notify_settings);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public isPeerMuted(peerID: number) {
|
||||
if(peerID == rootScope.myID) return false;
|
||||
|
||||
const dialog = this.getDialogByPeerID(peerID)[0];
|
||||
let muted = false;
|
||||
if(dialog && dialog.notify_settings && dialog.notify_settings.mute_until) {
|
||||
muted = new Date(dialog.notify_settings.mute_until * 1000) > new Date();
|
||||
}
|
||||
|
||||
return muted;
|
||||
}
|
||||
|
||||
public mutePeer(peerID: number) {
|
||||
let inputPeer = appPeersManager.getInputPeerByID(peerID);
|
||||
let inputNotifyPeer: InputNotifyPeer.inputNotifyPeer = {
|
||||
_: 'inputNotifyPeer',
|
||||
peer: inputPeer
|
||||
};
|
||||
|
||||
let settings: InputPeerNotifySettings = {
|
||||
_: 'inputPeerNotifySettings'
|
||||
};
|
||||
|
||||
let dialog = appMessagesManager.getDialogByPeerID(peerID)[0];
|
||||
let muted = true;
|
||||
if(dialog && dialog.notify_settings) {
|
||||
muted = dialog.notify_settings.mute_until > (Date.now() / 1000 | 0);
|
||||
}
|
||||
|
||||
if(!muted) {
|
||||
settings.mute_until = 2147483647;
|
||||
}
|
||||
|
||||
apiManager.invokeApi('account.updateNotifySettings', {
|
||||
peer: inputNotifyPeer,
|
||||
settings: settings
|
||||
}).then(bool => {
|
||||
if(bool) {
|
||||
this.handleUpdate({
|
||||
_: 'updateNotifySettings',
|
||||
peer: {
|
||||
_: 'notifyPeer',
|
||||
peer: appPeersManager.getOutputPeer(peerID)
|
||||
},
|
||||
notify_settings: { // ! WOW, IT WORKS !
|
||||
...settings,
|
||||
_: 'peerNotifySettings',
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/* return apiManager.invokeApi('account.getNotifySettings', {
|
||||
peer: inputNotifyPeer
|
||||
}).then((settings: any) => {
|
||||
settings.mute_until = 2000000000; // 2147483646
|
||||
|
||||
return apiManager.invokeApi('account.updateNotifySettings', {
|
||||
peer: inputNotifyPeer,
|
||||
settings: Object.assign(settings, {
|
||||
_: 'inputPeerNotifySettings'
|
||||
})
|
||||
}).then(res => {
|
||||
this.log('mute result:', res);
|
||||
});
|
||||
}); */
|
||||
|
||||
}
|
||||
|
||||
public canWriteToPeer(peerID: number) {
|
||||
const isChannel = appPeersManager.isChannel(peerID);
|
||||
const hasRights = isChannel && appChatsManager.hasRights(-peerID, 'send');
|
||||
|
||||
return (!isChannel || hasRights) && (peerID < 0 || appUsersManager.canSendToUser(peerID));
|
||||
}
|
||||
|
||||
public finalizePendingMessage(randomID: number, finalMessage: any) {
|
||||
var pendingData = this.pendingByRandomID[randomID];
|
||||
// this.log('pdata', randomID, pendingData)
|
||||
@ -3908,6 +3999,31 @@ export class AppMessagesManager {
|
||||
delete this.tempFinalizeCallbacks[tempID];
|
||||
}
|
||||
|
||||
// set cached url to media
|
||||
const message = appMessagesManager.getMessage(mid);
|
||||
if(message.media) {
|
||||
if(message.media.photo) {
|
||||
const photo = appPhotosManager.getPhoto('' + tempID);
|
||||
if(/* photo._ != 'photoEmpty' */photo) {
|
||||
const newPhoto = message.media.photo;
|
||||
// костыль
|
||||
defineNotNumerableProperties(newPhoto, ['downloaded', 'url']);
|
||||
newPhoto.downloaded = photo.downloaded;
|
||||
newPhoto.url = photo.url;
|
||||
}
|
||||
} else if(message.media.document) {
|
||||
const doc = appDocsManager.getDoc('' + tempID);
|
||||
if(/* doc._ != 'documentEmpty' && */doc?.type && doc.type != 'sticker') {
|
||||
const newDoc = message.media.document;
|
||||
newDoc.downloaded = doc.downloaded;
|
||||
newDoc.url = doc.url;
|
||||
}
|
||||
} else if(message.media.poll) {
|
||||
delete appPollsManager.polls[tempID];
|
||||
delete appPollsManager.results[tempID];
|
||||
}
|
||||
}
|
||||
|
||||
rootScope.broadcast('message_sent', {tempID, mid});
|
||||
}
|
||||
|
||||
@ -4286,7 +4402,7 @@ export class AppMessagesManager {
|
||||
}
|
||||
|
||||
public setTyping(peerID: number, _action: any): Promise<boolean> {
|
||||
if(!rootScope.myID) return Promise.resolve(false);
|
||||
if(!rootScope.myID || !peerID || !this.canWriteToPeer(peerID)) return Promise.resolve(false);
|
||||
|
||||
const action: SendMessageAction = typeof(_action) == 'string' ? {_: _action} : _action;
|
||||
return apiManager.invokeApi('messages.setTyping', {
|
||||
|
@ -70,7 +70,7 @@ export type Poll = {
|
||||
chosenIndexes?: number[]
|
||||
};
|
||||
|
||||
class AppPollsManager {
|
||||
export class AppPollsManager {
|
||||
public polls: {[id: string]: Poll} = {};
|
||||
public results: {[id: string]: PollResults} = {};
|
||||
|
||||
|
@ -6,7 +6,7 @@ import rootScope from "../rootScope";
|
||||
import { safeReplaceObject } from "../../helpers/object";
|
||||
import { limitSymbols } from "../../helpers/string";
|
||||
|
||||
class AppWebPagesManager {
|
||||
export class AppWebPagesManager {
|
||||
webpages: any = {};
|
||||
pendingWebPages: any = {};
|
||||
|
||||
|
@ -13,7 +13,7 @@ function dT() {
|
||||
}
|
||||
|
||||
export function logger(prefix: string, level = LogLevels.log | LogLevels.warn | LogLevels.error) {
|
||||
if(process.env.NODE_ENV != 'development'/* || true */) {
|
||||
if(process.env.NODE_ENV != 'development' || true) {
|
||||
level = LogLevels.error;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@ import RLottieWorker from 'worker-loader!./rlottie/rlottie.worker';
|
||||
import animationIntersector from "../components/animationIntersector";
|
||||
import EventListenerBase from "../helpers/eventListenerBase";
|
||||
import mediaSizes from "../helpers/mediaSizes";
|
||||
import { clamp } from '../helpers/number';
|
||||
import { isAndroid, isApple, isAppleMobile, isSafari } from "../helpers/userAgent";
|
||||
import { logger, LogLevels } from "./logger";
|
||||
import apiManager from "./mtproto/mtprotoworker";
|
||||
@ -91,7 +92,7 @@ export class RLottiePlayer extends EventListenerBase<{
|
||||
}
|
||||
}
|
||||
|
||||
// Skip ratio
|
||||
// * Skip ratio (30fps)
|
||||
let skipRatio: number;
|
||||
if(options.skipRatio !== undefined) skipRatio = options.skipRatio;
|
||||
else if((isAndroid || isAppleMobile || (isApple && !isSafari)) && this.width < 100 && this.height < 100) {
|
||||
@ -100,8 +101,11 @@ export class RLottiePlayer extends EventListenerBase<{
|
||||
|
||||
this.skipDelta = skipRatio !== undefined ? 1 / skipRatio | 0 : 1;
|
||||
|
||||
// Pixel ratio
|
||||
const pixelRatio = window.devicePixelRatio;
|
||||
//options.needUpscale = true;
|
||||
|
||||
// * Pixel ratio
|
||||
//const pixelRatio = window.devicePixelRatio;
|
||||
const pixelRatio = clamp(window.devicePixelRatio, 1, 2);
|
||||
if(pixelRatio > 1) {
|
||||
//this.cachingEnabled = true;//this.width < 100 && this.height < 100;
|
||||
if(options.needUpscale) {
|
||||
@ -125,7 +129,7 @@ export class RLottiePlayer extends EventListenerBase<{
|
||||
}
|
||||
}
|
||||
|
||||
// Cache frames params
|
||||
// * Cache frames params
|
||||
if(!options.noCache) {
|
||||
// проверка на размер уже после скейлинга, сделано для попапа и сайдбара, где стикеры 80х80 и 68х68, туда нужно 75%
|
||||
if(isApple && this.width > 100 && this.height > 100) {
|
||||
|
@ -1160,9 +1160,14 @@ export default class MTPNetworker {
|
||||
|
||||
// * https://core.telegram.org/mtproto/service_messages_about_messages#notice-of-ignored-error-message
|
||||
public processMessage(message: any, messageID: string, sessionID: Uint8Array | number[]) {
|
||||
if(message._ == 'messageEmpty') {
|
||||
this.log.warn('processMessage: messageEmpty', message, messageID);
|
||||
return;
|
||||
}
|
||||
|
||||
const msgidInt = parseInt(messageID.substr(0, -10), 10);
|
||||
if(msgidInt % 2) {
|
||||
this.log.warn('[MT] Server even message id: ', messageID, message);
|
||||
this.log.warn('Server even message id: ', messageID, message);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import type { AppMessagesManager, Dialog } from "./appManagers/appMessagesManage
|
||||
import type { Poll, PollResults } from "./appManagers/appPollsManager";
|
||||
import type { MyDialogFilter } from "./storages/filters";
|
||||
import type { ConnectionStatusChange } from "../types";
|
||||
import type { UserTyping } from "./appManagers/appChatsManager";
|
||||
import { MOUNT_CLASS_TO, UserAuth } from "./mtproto/mtproto_config";
|
||||
|
||||
type BroadcastEvents = {
|
||||
@ -11,6 +12,7 @@ type BroadcastEvents = {
|
||||
'user_auth': UserAuth,
|
||||
'peer_changed': number,
|
||||
'peer_pinned_messages': number,
|
||||
'peer_typings': {peerID: number, typings: UserTyping[]},
|
||||
|
||||
'filter_delete': MyDialogFilter,
|
||||
'filter_update': MyDialogFilter,
|
||||
@ -70,7 +72,6 @@ type BroadcastEvents = {
|
||||
|
||||
class RootScope {
|
||||
public overlayIsActive: boolean = false;
|
||||
public selectedPeerID = 0;
|
||||
public myID = 0;
|
||||
public idle = {
|
||||
isIDLE: false
|
||||
@ -102,10 +103,14 @@ class RootScope {
|
||||
document.addEventListener(name, callback);
|
||||
};
|
||||
|
||||
public addEventListener = this.on;
|
||||
|
||||
public off = <T extends keyof BroadcastEvents>(name: T, callback: (e: Omit<CustomEvent, 'detail'> & {detail: BroadcastEvents[T]}) => any) => {
|
||||
// @ts-ignore
|
||||
document.removeEventListener(name, callback);
|
||||
};
|
||||
|
||||
public removeEventListener = this.off;
|
||||
}
|
||||
|
||||
const rootScope = new RootScope();
|
||||
|
@ -13,194 +13,7 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
} */
|
||||
|
||||
#topbar {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
user-select: none;
|
||||
box-shadow: 0px 1px 5px -1px rgba(0, 0, 0, .21);
|
||||
z-index: 1;
|
||||
min-height: 3.5rem;
|
||||
max-height: 3.5rem;
|
||||
// border-bottom: 1px solid #DADCE0;
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
&.is-pinned-audio-shown, &.is-pinned-message-shown:not(.hide-pinned) {
|
||||
& + #bubbles {
|
||||
margin-top: 52px;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-pinned-message-shown:not(.hide-pinned):not(.is-pinned-audio-shown) {
|
||||
.pinned-message {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include respond-to(not-handhelds) {
|
||||
border-left: 1px solid #DADCE0;
|
||||
border-right: 1px solid #DADCE0;
|
||||
|
||||
.menu-search {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.is-pinned-message-shown:not(.hide-pinned) {
|
||||
.pinned-message {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include respond-to(no-floating-left-sidebar) {
|
||||
.sidebar-close-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* @include respond-to(wide-screens) {
|
||||
transition: .2s ease-in-out;
|
||||
align-self: start;
|
||||
|
||||
body.is-right-column-shown & {
|
||||
width: calc(100% - (#{$large-screen} / 4));
|
||||
}
|
||||
|
||||
body.animation-level-0 & {
|
||||
transition: none;
|
||||
}
|
||||
} */
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
.chat-mute-button, .chat-search-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* @include respond-to(handhelds) {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 3;
|
||||
} */
|
||||
|
||||
.chat-more-button {
|
||||
.btn-menu {
|
||||
top: calc(100% + 7px);
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
top: 29px;
|
||||
}
|
||||
}
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-info {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
//--utils-width: NaN;
|
||||
|
||||
//&.have-utils-width {
|
||||
max-width: calc(100% - var(--utils-width));
|
||||
|
||||
@include respond-to(medium-screens) {
|
||||
body.is-right-column-shown & {
|
||||
max-width: calc(100% - var(--right-column-width) - var(--utils-width));
|
||||
}
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
.chat-utils {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
|
||||
/* position: absolute;
|
||||
right: 0px;
|
||||
padding-right: inherit; */
|
||||
|
||||
@include respond-to(medium-screens) {
|
||||
transition: transform var(--layer-transition);
|
||||
|
||||
body.is-right-column-shown & {
|
||||
transform: translate3d(calc(var(--right-column-width) * -1), 0, 0);
|
||||
}
|
||||
|
||||
body.animation-level-0 & {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-join {
|
||||
width: auto;
|
||||
width: 117px;
|
||||
height: 36px;
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
margin-right: .5rem;
|
||||
|
||||
&:not(.hide) + .chat-mute-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1 1 auto;
|
||||
padding-left: 10px;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.person {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
margin-left: 7px;
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
font-size: 14px;
|
||||
//line-height: 18px;
|
||||
color: #707579;
|
||||
|
||||
.online {
|
||||
color: $color-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#im-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
//font-size: 16px;
|
||||
font-size: 16px;
|
||||
flex: 0 0 auto;
|
||||
|
||||
&:before {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
&.tgico-avatar_deletedaccount:before {
|
||||
font-size: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
&.hide-pinned + #bubbles {
|
||||
#bubbles-inner {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#chat-input {
|
||||
.chat-input {
|
||||
--translateY: 0;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
@ -220,11 +33,11 @@ $chat-helper-size: 39px;
|
||||
@include respond-to(esg-top) {
|
||||
/* flex: 0 0 auto;
|
||||
height: auto; */
|
||||
max-width: var(--messages-container-width);
|
||||
max-width: var(--messages-container-width) !important;
|
||||
}
|
||||
|
||||
@include respond-to(medium-screens) {
|
||||
width: calc(100% - var(--right-column-width));
|
||||
width: calc(100% - var(--right-column-width)) !important;
|
||||
//transition: transform var(--layer-transition);
|
||||
|
||||
body.is-right-column-shown & {
|
||||
@ -242,7 +55,7 @@ $chat-helper-size: 39px;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
|
||||
#bubbles.is-selecting:not(.backwards) ~ & {
|
||||
.bubbles.is-selecting:not(.backwards) ~ & {
|
||||
--translateY: 0;
|
||||
}
|
||||
}
|
||||
@ -286,7 +99,7 @@ $chat-helper-size: 39px;
|
||||
overflow: hidden;
|
||||
} */
|
||||
|
||||
#input-message {
|
||||
.input-message-input {
|
||||
background: none;
|
||||
border: none;
|
||||
width: 100%;
|
||||
@ -321,7 +134,7 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
}
|
||||
|
||||
#btn-record-cancel {
|
||||
.btn-record-cancel {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
transition: visibility 0s .1s, opacity .1s 0s;
|
||||
@ -347,7 +160,7 @@ $chat-helper-size: 39px;
|
||||
padding-bottom: inherit;
|
||||
}
|
||||
|
||||
#btn-send {
|
||||
.btn-send {
|
||||
color: #9e9e9e;
|
||||
|
||||
> .tgico {
|
||||
@ -357,7 +170,7 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
|
||||
&.send {
|
||||
color: $color-blue;
|
||||
color: $color-blue !important;
|
||||
}
|
||||
|
||||
&.send .tgico-send,
|
||||
@ -370,10 +183,10 @@ $chat-helper-size: 39px;
|
||||
transition: .2s color, background-color .2s, .2s opacity;
|
||||
}
|
||||
|
||||
#btn-record-cancel, #btn-send {
|
||||
.btn-record-cancel, .btn-send {
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.5rem;
|
||||
background-color: #fff;
|
||||
background-color: #fff !important;
|
||||
}
|
||||
|
||||
.record-time {
|
||||
@ -420,24 +233,24 @@ $chat-helper-size: 39px;
|
||||
color: #c6cbce;
|
||||
}
|
||||
|
||||
&:not(.is-recording) #btn-send {
|
||||
&:not(.is-recording) .btn-send {
|
||||
color: #c6cbce;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-recording {
|
||||
#btn-record-cancel {
|
||||
.btn-record-cancel {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
transition: visibility 0s .1s, opacity .1s .1s;
|
||||
}
|
||||
|
||||
// unlock
|
||||
#btn-send, #btn-record-cancel {
|
||||
.btn-send, .btn-record-cancel {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.input-message {
|
||||
.rows-wrapper {
|
||||
width: calc(100% - #{$chat-input-size * 2 + $btn-send-margin * 2});
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
@ -445,7 +258,7 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
}
|
||||
|
||||
#attach-file {
|
||||
.attach-file {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@ -460,13 +273,13 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
|
||||
&:not(.is-recording) {
|
||||
#btn-record-cancel {
|
||||
.btn-record-cancel {
|
||||
margin-right: 0;
|
||||
width: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
#bubbles.is-selecting ~ & {
|
||||
.bubbles.is-selecting ~ & {
|
||||
.new-message-wrapper {
|
||||
html:not(.is-safari) & {
|
||||
transition: .1s opacity;
|
||||
@ -477,13 +290,13 @@ $chat-helper-size: 39px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
#btn-send {
|
||||
.btn-send {
|
||||
html:not(.is-safari) & {
|
||||
transition: .2s transform;
|
||||
}
|
||||
}
|
||||
|
||||
.input-message {
|
||||
.rows-wrapper {
|
||||
html:not(.is-safari) & {
|
||||
transition: width .2s, border-bottom-right-radius .1s, transform .2s;
|
||||
|
||||
@ -500,7 +313,7 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
}
|
||||
|
||||
#bubbles.is-selecting:not(.backwards) ~ & {
|
||||
.bubbles.is-selecting:not(.backwards) ~ & {
|
||||
.new-message-wrapper {
|
||||
opacity: 0;
|
||||
}
|
||||
@ -515,7 +328,7 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
}
|
||||
|
||||
.input-message {
|
||||
.rows-wrapper {
|
||||
max-height: $chat-input-size;
|
||||
border-bottom-right-radius: 12px;
|
||||
transform-origin: left;
|
||||
@ -556,14 +369,14 @@ $chat-helper-size: 39px;
|
||||
border-radius: inherit;
|
||||
}
|
||||
|
||||
#btn-send {
|
||||
.btn-send {
|
||||
transform: scale(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#bubbles.is-selecting.backwards ~ & {
|
||||
.bubbles.is-selecting.backwards ~ & {
|
||||
.new-message-wrapper {
|
||||
html:not(.is-safari) & {
|
||||
transition-delay: .1s;
|
||||
@ -593,41 +406,8 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
}
|
||||
|
||||
#im-title {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
max-width: calc(100% - 1.5rem);
|
||||
|
||||
/* @include respond-to(handhelds) {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
} */
|
||||
|
||||
span.emoji {
|
||||
vertical-align: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
#im-title, #im-subtitle {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#im-subtitle {
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
// !WARNING, уже нельзя переименовать в ID, так как это заденет другие селекторы и вёрстка в чате поедет, например - стикер вместе с реплаем
|
||||
.chat-container {
|
||||
display: flex;
|
||||
// padding: 200px;
|
||||
#column-center {
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
//overflow: hidden;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
flex: 3;
|
||||
|
||||
@ -648,8 +428,21 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-background {
|
||||
.chats-container {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chat {
|
||||
display: flex;
|
||||
// padding: 200px;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
//overflow: hidden;
|
||||
flex-direction: column;
|
||||
|
||||
&-background {
|
||||
overflow: hidden;
|
||||
|
||||
&.no-transition:before {
|
||||
@ -688,7 +481,7 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
}
|
||||
|
||||
.input-message {
|
||||
.rows-wrapper {
|
||||
--padding-vertical: .3125rem;
|
||||
--padding-horizontal: .5rem;
|
||||
--padding: var(--padding-vertical) var(--padding-horizontal);
|
||||
@ -760,7 +553,7 @@ $chat-helper-size: 39px;
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
#attach-file {
|
||||
.attach-file {
|
||||
&.menu-open {
|
||||
color: $color-blue;
|
||||
background-color: transparent;
|
||||
@ -806,7 +599,7 @@ $chat-helper-size: 39px;
|
||||
padding-top: .25rem;
|
||||
}
|
||||
|
||||
.chat-container.is-helper-active & {
|
||||
.chat.is-helper-active & {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
transform: translateY(#{-$chat-helper-size});
|
||||
@ -934,7 +727,7 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
}
|
||||
|
||||
#bubbles {
|
||||
.bubbles {
|
||||
--translateY: 0;
|
||||
/* overflow-y: scroll;
|
||||
scrollbar-width: none;
|
||||
@ -953,11 +746,11 @@ $chat-helper-size: 39px;
|
||||
-webkit-mask-image: -webkit-radial-gradient(circle, white 100%, black 100%); // fix safari overflow
|
||||
} */
|
||||
|
||||
.chat-container.is-helper-active & {
|
||||
.chat.is-helper-active & {
|
||||
&:not(.is-selecting), &.is-selecting.backwards {
|
||||
--translateY: -#{$chat-helper-size};
|
||||
|
||||
#bubbles-inner {
|
||||
.bubbles-inner {
|
||||
transform: translateY(calc(var(--translateY) * -1));
|
||||
//margin-top: $chat-helper-size;
|
||||
//transition: none;
|
||||
@ -972,14 +765,14 @@ $chat-helper-size: 39px;
|
||||
--translateY: -58px;
|
||||
}
|
||||
|
||||
#bubbles-inner {
|
||||
.bubbles-inner {
|
||||
transform: translateY(calc(var(--translateY) * -1));
|
||||
//margin-top: $chat-helper-size;
|
||||
//transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* #bubbles-transform-helper {
|
||||
/* .bubbles-transform-helper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
@ -1041,14 +834,14 @@ $chat-helper-size: 39px;
|
||||
} */
|
||||
|
||||
&:not(.scrolled-down):not(.search-results-active) {
|
||||
//> #bubbles-transform-helper {
|
||||
//> .bubbles-transform-helper {
|
||||
// ! these lines will blur messages if chat input helper is active
|
||||
> .scrollable {
|
||||
-webkit-mask-image: -webkit-linear-gradient(bottom, transparent, #000 20px);
|
||||
mask-image: linear-gradient(0deg, transparent 0, #000 20px);
|
||||
}
|
||||
|
||||
> #bubbles-go-down {
|
||||
> .bubbles-go-down {
|
||||
cursor: pointer;
|
||||
--translateY: 0;
|
||||
opacity: 1;
|
||||
@ -1079,7 +872,7 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
}
|
||||
|
||||
#bubbles-inner {
|
||||
.bubbles-inner {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@ -1132,7 +925,7 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
}
|
||||
|
||||
/* #bubbles.is-chat-input-hidden & {
|
||||
/* .bubbles.is-chat-input-hidden & {
|
||||
padding-bottom: 55px;
|
||||
} */
|
||||
|
||||
@ -1143,7 +936,7 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
|
||||
&.is-scrolling .is-sticky {
|
||||
opacity: 0.99999; // 0.99999 сделано для сафари, т.к. без этого будет прыжок при скролле в самом низу или верху
|
||||
opacity: 0.99999 !important; // 0.99999 сделано для сафари, т.к. без этого будет прыжок при скролле в самом низу или верху
|
||||
|
||||
html.is-safari & {
|
||||
transform: translateY(calc(var(--translateY) * -1));
|
||||
@ -1152,7 +945,7 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
}
|
||||
|
||||
#bubbles-go-down {
|
||||
.bubbles-go-down {
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
@ -1224,7 +1017,7 @@ $chat-helper-size: 39px;
|
||||
color: #949596;
|
||||
}
|
||||
|
||||
& + #chat-input {
|
||||
& + .chat-input {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@ -1325,140 +1118,3 @@ $chat-helper-size: 39px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.markup-tooltip {
|
||||
$widthRegular: 218px;
|
||||
$widthLink: 420px;
|
||||
$padding: 7px;
|
||||
|
||||
background: #fff;
|
||||
border-radius: $border-radius-medium;
|
||||
transform: translateZ(0);
|
||||
opacity: 0;
|
||||
transition: opacity var(--layer-transition), transform var(--layer-transition), width var(--layer-transition);
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 44px;
|
||||
width: $widthRegular;
|
||||
overflow: hidden;
|
||||
|
||||
&-wrapper {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: start;
|
||||
//width: 420px;
|
||||
width: #{$widthRegular + $widthLink};
|
||||
height: 100%;
|
||||
transform: translateX(0);
|
||||
transition: transform var(--layer-transition);
|
||||
}
|
||||
|
||||
&-tools {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: $padding;
|
||||
|
||||
&:first-child {
|
||||
width: $widthRegular;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
width: $widthLink;
|
||||
|
||||
.markup-tooltip-delimiter {
|
||||
margin-left: .25rem;
|
||||
margin-right: .75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-delimiter {
|
||||
width: 1px;
|
||||
height: 25px;
|
||||
background-color: #DADCE0;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
border-radius: $border-radius !important;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
padding: 0;
|
||||
|
||||
&.active {
|
||||
color: #fff!important;
|
||||
background-color: $color-blue!important;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.is-visible) {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.is-visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
&.is-link {
|
||||
width: $widthLink;
|
||||
}
|
||||
|
||||
&.is-link &-wrapper {
|
||||
transform: translateX(#{-$widthRegular});
|
||||
}
|
||||
|
||||
.input-clear {
|
||||
flex: 1 1 auto;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
&.no-transition {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
.stickers-helper {
|
||||
position: absolute !important;
|
||||
bottom: calc(100% + 10px);
|
||||
opacity: 0;
|
||||
transition: opacity .2s ease-in-out;
|
||||
overflow: hidden;
|
||||
padding: 0 !important;
|
||||
|
||||
> .scrollable {
|
||||
position: relative;
|
||||
max-height: 220px;
|
||||
min-height: var(--esg-sticker-size);
|
||||
}
|
||||
|
||||
&-stickers {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&-sticker {
|
||||
position: relative;
|
||||
width: var(--esg-sticker-size);
|
||||
height: var(--esg-sticker-size);
|
||||
margin: 5px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.is-visible) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.is-visible {
|
||||
&:not(.backwards) {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ $bubble-margin: .25rem;
|
||||
margin: 0 auto;
|
||||
user-select: none;
|
||||
|
||||
&.is-highlighted, &.is-selected, /* #bubbles.is-selecting */ & {
|
||||
&.is-highlighted, &.is-selected, /* .bubbles.is-selecting */ & {
|
||||
&:after {
|
||||
position: absolute;
|
||||
left: -50%;
|
||||
@ -158,7 +158,7 @@ $bubble-margin: .25rem;
|
||||
&.is-sticky {
|
||||
opacity: .00001; // for safari
|
||||
|
||||
#bubbles-inner:not(.is-scrolling) & {
|
||||
.bubbles-inner:not(.is-scrolling) & {
|
||||
//transition-delay: 1.35s;
|
||||
|
||||
.bubble__container {
|
||||
@ -166,7 +166,7 @@ $bubble-margin: .25rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* #bubbles-inner.is-scrolling & {
|
||||
/* .bubbles-inner.is-scrolling & {
|
||||
transition-delay: 0;
|
||||
} */
|
||||
}
|
||||
@ -230,7 +230,7 @@ $bubble-margin: .25rem;
|
||||
}
|
||||
}
|
||||
|
||||
#bubbles.is-selecting &:not(.is-album) {
|
||||
.bubbles.is-selecting &:not(.is-album) {
|
||||
.audio, .document, .attachment, poll-element {
|
||||
pointer-events: none !important;
|
||||
}
|
||||
@ -252,8 +252,8 @@ $bubble-margin: .25rem;
|
||||
transition: .2s transform;
|
||||
user-select: none;
|
||||
|
||||
html.no-touch #bubbles:not(.is-selecting) &,
|
||||
html.is-touch #bubbles.is-selecting:not(.no-select) & {
|
||||
html.no-touch .bubbles:not(.is-selecting) &,
|
||||
html.is-touch .bubbles.is-selecting:not(.no-select) & {
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
@ -276,13 +276,13 @@ $bubble-margin: .25rem;
|
||||
font-size: 1rem;
|
||||
cursor: pointer;
|
||||
|
||||
#bubbles.is-selecting & {
|
||||
.bubbles.is-selecting & {
|
||||
transform: scale3d(1, 1, 1);
|
||||
transform-origin: bottom;
|
||||
transition: transform var(--layer-transition);
|
||||
}
|
||||
|
||||
#bubbles.is-selecting:not(.backwards) & {
|
||||
.bubbles.is-selecting:not(.backwards) & {
|
||||
transform: scale3d(.76, .76, 1);
|
||||
}
|
||||
|
||||
@ -350,7 +350,7 @@ $bubble-margin: .25rem;
|
||||
bottom: 8px;
|
||||
}
|
||||
|
||||
#bubbles-inner.is-chat &.is-in {
|
||||
.bubbles-inner.is-chat &.is-in {
|
||||
.bubble-select-checkbox {
|
||||
bottom: 7px;
|
||||
}
|
||||
@ -641,7 +641,7 @@ $bubble-margin: .25rem;
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
|
||||
/* #bubbles.is-selecting & {
|
||||
/* .bubbles.is-selecting & {
|
||||
-webkit-mask-image: -webkit-radial-gradient(circle, white 100%, black 100%); // fix safari overflow
|
||||
} */
|
||||
|
||||
@ -1014,6 +1014,8 @@ $bubble-margin: .25rem;
|
||||
&-details {
|
||||
padding-left: 12px;
|
||||
margin-top: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
&-name {
|
||||
|
94
src/scss/partials/_chatMarkupTooltip.scss
Normal file
94
src/scss/partials/_chatMarkupTooltip.scss
Normal file
@ -0,0 +1,94 @@
|
||||
.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;
|
||||
}
|
||||
}
|
41
src/scss/partials/_chatStickersHelper.scss
Normal file
41
src/scss/partials/_chatStickersHelper.scss
Normal file
@ -0,0 +1,41 @@
|
||||
.stickers-helper {
|
||||
position: absolute !important;
|
||||
bottom: calc(100% + 10px);
|
||||
opacity: 0;
|
||||
transition: opacity .2s ease-in-out;
|
||||
overflow: hidden;
|
||||
padding: 0 !important;
|
||||
|
||||
> .scrollable {
|
||||
position: relative;
|
||||
max-height: 220px;
|
||||
min-height: var(--esg-sticker-size);
|
||||
}
|
||||
|
||||
&-stickers {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
&-sticker {
|
||||
position: relative;
|
||||
width: var(--esg-sticker-size);
|
||||
height: var(--esg-sticker-size);
|
||||
margin: 5px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.is-visible) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.is-visible {
|
||||
&:not(.backwards) {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
223
src/scss/partials/_chatTopbar.scss
Normal file
223
src/scss/partials/_chatTopbar.scss
Normal file
@ -0,0 +1,223 @@
|
||||
.topbar {
|
||||
width: 100%;
|
||||
background-color: #fff;
|
||||
user-select: none;
|
||||
box-shadow: 0px 1px 5px -1px rgba(0, 0, 0, .21);
|
||||
z-index: 1;
|
||||
min-height: 3.5rem;
|
||||
max-height: 3.5rem;
|
||||
// border-bottom: 1px solid #DADCE0;
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
&.is-pinned-audio-shown, &.is-pinned-message-shown:not(.hide-pinned) {
|
||||
& + .bubbles {
|
||||
margin-top: 52px;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-pinned-message-shown:not(.hide-pinned):not(.is-pinned-audio-shown) {
|
||||
.pinned-message {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include respond-to(not-handhelds) {
|
||||
border-left: 1px solid #DADCE0;
|
||||
border-right: 1px solid #DADCE0;
|
||||
|
||||
.menu-search {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.is-pinned-message-shown:not(.hide-pinned) {
|
||||
.pinned-message {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include respond-to(no-floating-left-sidebar) {
|
||||
.chat:first-child & {
|
||||
.sidebar-close-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chat-info {
|
||||
padding-left: 7px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-close-button {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
/* @include respond-to(wide-screens) {
|
||||
transition: .2s ease-in-out;
|
||||
align-self: start;
|
||||
|
||||
body.is-right-column-shown & {
|
||||
width: calc(100% - (#{$large-screen} / 4));
|
||||
}
|
||||
|
||||
body.animation-level-0 & {
|
||||
transition: none;
|
||||
}
|
||||
} */
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
.chat-mute-button, .chat-search-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* @include respond-to(handhelds) {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 3;
|
||||
} */
|
||||
|
||||
.user-title {
|
||||
cursor: pointer;
|
||||
font-size: 18px;
|
||||
line-height: 24px;
|
||||
max-width: calc(100% - 1.5rem);
|
||||
|
||||
/* @include respond-to(handhelds) {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
} */
|
||||
|
||||
span.emoji {
|
||||
vertical-align: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.user-title, .info {
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.info {
|
||||
margin-top: -2px;
|
||||
}
|
||||
|
||||
.btn-menu-toggle {
|
||||
.btn-menu {
|
||||
top: calc(100% + 7px);
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
top: 29px;
|
||||
}
|
||||
}
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-info {
|
||||
flex: 1 1 auto;
|
||||
overflow: hidden;
|
||||
padding-left: 49px;
|
||||
//--utils-width: NaN;
|
||||
|
||||
//&.have-utils-width {
|
||||
max-width: calc(100% - var(--utils-width));
|
||||
|
||||
@include respond-to(medium-screens) {
|
||||
body.is-right-column-shown & {
|
||||
max-width: calc(100% - var(--right-column-width) - var(--utils-width));
|
||||
}
|
||||
}
|
||||
//}
|
||||
}
|
||||
|
||||
.chat-utils {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
|
||||
/* position: absolute;
|
||||
right: 0px;
|
||||
padding-right: inherit; */
|
||||
|
||||
@include respond-to(medium-screens) {
|
||||
transition: transform var(--layer-transition);
|
||||
|
||||
body.is-right-column-shown & {
|
||||
transform: translate3d(calc(var(--right-column-width) * -1), 0, 0);
|
||||
}
|
||||
|
||||
body.animation-level-0 & {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chat-join {
|
||||
width: auto;
|
||||
width: 117px;
|
||||
height: 36px;
|
||||
font-weight: 400;
|
||||
font-size: 0.875rem;
|
||||
margin-right: .5rem;
|
||||
|
||||
&:not(.hide) + .chat-mute-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
flex: 1 1 auto;
|
||||
padding-left: 10px;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.person {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.bottom {
|
||||
font-size: 14px;
|
||||
//line-height: 18px;
|
||||
color: #707579;
|
||||
|
||||
.online {
|
||||
color: $color-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
avatar-element {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
//font-size: 16px;
|
||||
font-size: 16px;
|
||||
flex: 0 0 auto;
|
||||
|
||||
&:before {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
&.tgico-avatar_deletedaccount:before {
|
||||
font-size: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
&.hide-pinned + .bubbles {
|
||||
.bubbles-inner {
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
.chats-container {
|
||||
.chatlist-container {
|
||||
position: relative;
|
||||
|
||||
/* .scrollable {
|
||||
@ -391,7 +391,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
// use together like class="chats-container contacts-container"
|
||||
// use together like class="chatlist-container contacts-container"
|
||||
.contacts-container, .search-group-contacts {
|
||||
.dialog-avatar {
|
||||
width: 48px;
|
||||
|
@ -109,7 +109,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
#chats-container {
|
||||
#chatlist-container {
|
||||
max-height: 100%;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
@ -393,7 +393,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.chats-container {
|
||||
.chatlist-container {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
@ -690,7 +690,7 @@
|
||||
color: #50a2e9;
|
||||
}
|
||||
|
||||
.popup-forward, .included-chats-container {
|
||||
.popup-forward, .included-chatlist-container {
|
||||
.selector {
|
||||
ul {
|
||||
li > .rp {
|
||||
@ -721,7 +721,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.included-chats-container {
|
||||
.included-chatlist-container {
|
||||
.sidebar-left-h2 {
|
||||
color: #707579;
|
||||
font-size: 15px;
|
||||
|
@ -51,7 +51,7 @@
|
||||
}
|
||||
|
||||
#search-private-container {
|
||||
.chats-container {
|
||||
.chatlist-container {
|
||||
position: relative;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
@ -150,7 +150,7 @@
|
||||
margin-top: 3px;
|
||||
}
|
||||
|
||||
&.online {
|
||||
.online {
|
||||
color: $color-blue;
|
||||
}
|
||||
}
|
||||
|
@ -75,7 +75,7 @@
|
||||
}
|
||||
|
||||
@include respond-to(until-floating-left-sidebar) {
|
||||
.chats-container ul li > .rp {
|
||||
.chatlist-container ul li > .rp {
|
||||
.c-ripple {
|
||||
--ripple-duration: .2s;
|
||||
|
||||
|
@ -111,7 +111,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.chats-container {
|
||||
.chatlist-container {
|
||||
height: 100%;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.selector, .chats-container {
|
||||
.selector, .chatlist-container {
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
|
@ -108,8 +108,11 @@ $messages-container-width: 728px;
|
||||
@import "partials/checkbox";
|
||||
@import "partials/chatlist";
|
||||
@import "partials/chat";
|
||||
@import "partials/chatTopbar";
|
||||
@import "partials/chatBubble";
|
||||
@import "partials/chatPinned";
|
||||
@import "partials/chatMarkupTooltip";
|
||||
@import "partials/chatStickersHelper";
|
||||
@import "partials/sidebar";
|
||||
@import "partials/leftSidebar";
|
||||
@import "partials/rightSidebar";
|
||||
|
Loading…
x
Reference in New Issue
Block a user