Refactor messages
Scheduled messages Respect stickers sending rights Edit icon
This commit is contained in:
parent
122c34fd4a
commit
d76ed7320e
|
@ -193,7 +193,7 @@ class AppMediaPlaybackController {
|
|||
return;
|
||||
}
|
||||
|
||||
for(let m of value.history) {
|
||||
for(const {mid: m} of value.history) {
|
||||
if(m > mid) {
|
||||
this.nextMid = m;
|
||||
} else if(m < mid) {
|
||||
|
|
|
@ -20,7 +20,7 @@ import { ButtonMenuItemOptions } from "./buttonMenu";
|
|||
import ButtonMenuToggle from "./buttonMenuToggle";
|
||||
import { LazyLoadQueueBase } from "./lazyLoadQueue";
|
||||
import { renderImageFromUrl } from "./misc";
|
||||
import PopupForward from "./popupForward";
|
||||
import PopupForward from "./popups/forward";
|
||||
import ProgressivePreloader from "./preloader";
|
||||
import Scrollable from "./scrollable";
|
||||
import appSidebarRight from "./sidebarRight";
|
||||
|
@ -1261,8 +1261,8 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
|
|||
}
|
||||
|
||||
const method = older ? value.history.forEach : value.history.forEachReverse;
|
||||
method.call(value.history, mid => {
|
||||
const message = appMessagesManager.getMessageByPeer(this.peerId, mid);
|
||||
method.call(value.history, message => {
|
||||
const mid = message.mid
|
||||
const media = this.getMediaFromMessage(message);
|
||||
|
||||
if(!media) return;
|
||||
|
|
|
@ -251,15 +251,13 @@ export default class AppSearch {
|
|||
|
||||
const {count, history, next_rate} = res;
|
||||
|
||||
if(history[0] == this.minMsgId) {
|
||||
if(history[0].mid == this.minMsgId) {
|
||||
history.shift();
|
||||
}
|
||||
|
||||
const searchGroup = this.searchGroups.messages;
|
||||
|
||||
history.forEach((msgId: number) => {
|
||||
const message = appMessagesManager.getMessageByPeer(this.peerId, msgId);
|
||||
|
||||
history.forEach((message: any) => {
|
||||
const {dialog, dom} = appDialogsManager.addDialogNew({
|
||||
dialog: message.peerId,
|
||||
container: this.scrollable/* searchGroup.list */,
|
||||
|
@ -271,7 +269,7 @@ export default class AppSearch {
|
|||
|
||||
searchGroup.toggle();
|
||||
|
||||
this.minMsgId = history[history.length - 1];
|
||||
this.minMsgId = history[history.length - 1].mid;
|
||||
this.offsetRate = next_rate;
|
||||
|
||||
if(this.loadedCount == -1) {
|
||||
|
|
|
@ -49,15 +49,14 @@ export default class AvatarElement extends HTMLElement {
|
|||
if(peerId < 0) {
|
||||
const maxId = Number.MAX_SAFE_INTEGER;
|
||||
const inputFilter = 'inputMessagesFilterChatPhotos';
|
||||
const mid = await appMessagesManager.getSearch(peerId, '', {_: inputFilter}, maxId, 2, 0, 1).then(value => {
|
||||
let message: any = await appMessagesManager.getSearch(peerId, '', {_: inputFilter}, maxId, 2, 0, 1).then(value => {
|
||||
//console.log(lol);
|
||||
// ! by descend
|
||||
return value.history[0];
|
||||
});
|
||||
|
||||
if(mid) {
|
||||
if(message) {
|
||||
// ! гений в деле, костылируем (но это гениально)
|
||||
let message = appMessagesManager.getMessageByPeer(peerId, mid);
|
||||
const messagePhoto = message.action.photo;
|
||||
if(messagePhoto.id != photo.id) {
|
||||
message = {
|
||||
|
|
|
@ -1,63 +1,77 @@
|
|||
import rootScope from "../../lib/rootScope";
|
||||
import { generatePathData } from "../../helpers/dom";
|
||||
import { MyMessage } from "../../lib/appManagers/appMessagesManager";
|
||||
|
||||
type BubbleGroup = {timestamp: number, fromId: number, mid: number, group: HTMLDivElement[]};
|
||||
type Group = {bubble: HTMLDivElement, mid: number, timestamp: number}[];
|
||||
type BubbleGroup = {timestamp: number, fromId: number, mid: number, group: Group};
|
||||
export default class BubbleGroups {
|
||||
bubblesByGroups: Array<BubbleGroup> = []; // map to group
|
||||
groups: Array<HTMLDivElement[]> = [];
|
||||
private bubbles: Array<BubbleGroup> = []; // map to group
|
||||
private groups: Array<Group> = [];
|
||||
//updateRAFs: Map<HTMLDivElement[], number> = new Map();
|
||||
newGroupDiff = 120;
|
||||
private newGroupDiff = 121; // * 121 in scheduled messages
|
||||
|
||||
removeBubble(bubble: HTMLDivElement, mid: number) {
|
||||
let details = this.bubblesByGroups.findAndSplice(g => g.mid == mid);
|
||||
const details = this.bubbles.findAndSplice(g => g.mid === mid);
|
||||
if(details && details.group.length) {
|
||||
details.group.findAndSplice(d => d == bubble);
|
||||
details.group.findAndSplice(d => d.bubble === bubble);
|
||||
if(!details.group.length) {
|
||||
this.groups.findAndSplice(g => g == details.group);
|
||||
this.groups.findAndSplice(g => g === details.group);
|
||||
} else {
|
||||
this.updateGroup(details.group);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addBubble(bubble: HTMLDivElement, message: any, reverse: boolean) {
|
||||
let timestamp = message.date;
|
||||
addBubble(bubble: HTMLDivElement, message: MyMessage, reverse: boolean) {
|
||||
const timestamp = message.date;
|
||||
const mid = message.mid;
|
||||
let fromId = message.fromId;
|
||||
let group: HTMLDivElement[];
|
||||
let group: Group;
|
||||
|
||||
// fix for saved messages forward to self
|
||||
if(fromId == rootScope.myId && message.peerId == rootScope.myId && message.fwdFromId == fromId) {
|
||||
if(fromId === rootScope.myId && message.peerId === rootScope.myId && (message as any).fwdFromId === fromId) {
|
||||
fromId = -fromId;
|
||||
}
|
||||
|
||||
// try to find added
|
||||
//this.removeBubble(message.mid);
|
||||
|
||||
if(this.bubblesByGroups.length) {
|
||||
if(reverse) {
|
||||
let g = this.bubblesByGroups[0];
|
||||
if(g.fromId == fromId && (g.timestamp - timestamp) < this.newGroupDiff) {
|
||||
group = g.group;
|
||||
group.unshift(bubble);
|
||||
} else {
|
||||
this.groups.unshift(group = [bubble]);
|
||||
}
|
||||
} else {
|
||||
let g = this.bubblesByGroups[this.bubblesByGroups.length - 1];
|
||||
if(g.fromId == fromId && (timestamp - g.timestamp) < this.newGroupDiff) {
|
||||
group = g.group;
|
||||
group.push(bubble);
|
||||
} else {
|
||||
this.groups.push(group = [bubble]);
|
||||
const insertObject = {bubble, mid, timestamp};
|
||||
if(this.bubbles.length) {
|
||||
const foundBubble = this.bubbles.find(bubble => {
|
||||
const diff = Math.abs(bubble.timestamp - timestamp);
|
||||
return bubble.fromId === fromId && diff <= this.newGroupDiff;
|
||||
});
|
||||
|
||||
if(!foundBubble) this.groups.push(group = [insertObject]);
|
||||
else {
|
||||
group = foundBubble.group;
|
||||
|
||||
let i = 0, foundMidOnSameTimestamp = 0;
|
||||
for(; i < group.length; ++i) {
|
||||
const _timestamp = group[i].timestamp;
|
||||
const _mid = group[i].mid;
|
||||
|
||||
if(timestamp < _timestamp) {
|
||||
break;
|
||||
} else if(timestamp === _timestamp) {
|
||||
foundMidOnSameTimestamp = _mid;
|
||||
}
|
||||
|
||||
if(foundMidOnSameTimestamp && mid < foundMidOnSameTimestamp) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
group.splice(i, 0, insertObject);
|
||||
}
|
||||
} else {
|
||||
this.groups.push(group = [bubble]);
|
||||
this.groups.push(group = [insertObject]);
|
||||
}
|
||||
|
||||
//console.log('[BUBBLE]: addBubble', bubble, message.mid, fromId, reverse, group);
|
||||
|
||||
this.bubblesByGroups[reverse ? 'unshift' : 'push']({timestamp, fromId, mid: message.mid, group});
|
||||
this.bubbles.push({timestamp, fromId, mid: message.mid, group});
|
||||
this.updateGroup(group);
|
||||
}
|
||||
|
||||
|
@ -112,7 +126,7 @@ export default class BubbleGroups {
|
|||
}
|
||||
}
|
||||
|
||||
updateGroup(group: HTMLDivElement[]) {
|
||||
updateGroup(group: Group) {
|
||||
/* if(this.updateRAFs.has(group)) {
|
||||
window.cancelAnimationFrame(this.updateRAFs.get(group));
|
||||
this.updateRAFs.delete(group);
|
||||
|
@ -125,7 +139,7 @@ export default class BubbleGroups {
|
|||
return;
|
||||
}
|
||||
|
||||
let first = group[0];
|
||||
const first = group[0].bubble;
|
||||
|
||||
//console.log('[BUBBLE]: updateGroup', group, first);
|
||||
|
||||
|
@ -139,14 +153,14 @@ export default class BubbleGroups {
|
|||
this.setClipIfNeeded(first, true);
|
||||
}
|
||||
|
||||
let length = group.length - 1;
|
||||
const length = group.length - 1;
|
||||
for(let i = 1; i < length; ++i) {
|
||||
let bubble = group[i];
|
||||
const bubble = group[i].bubble;
|
||||
bubble.classList.remove('is-group-last', 'is-group-first');
|
||||
this.setClipIfNeeded(bubble, true);
|
||||
}
|
||||
|
||||
let last = group[group.length - 1];
|
||||
const last = group[group.length - 1].bubble;
|
||||
last.classList.remove('is-group-first');
|
||||
last.classList.add('is-group-last');
|
||||
this.setClipIfNeeded(last);
|
||||
|
@ -154,14 +168,14 @@ export default class BubbleGroups {
|
|||
}
|
||||
|
||||
updateGroupByMessageId(mid: number) {
|
||||
let details = this.bubblesByGroups.find(g => g.mid == mid);
|
||||
const details = this.bubbles.find(g => g.mid == mid);
|
||||
if(details) {
|
||||
this.updateGroup(details.group);
|
||||
}
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
this.bubblesByGroups = [];
|
||||
this.bubbles = [];
|
||||
this.groups = [];
|
||||
/* for(let value of this.updateRAFs.values()) {
|
||||
window.cancelAnimationFrame(value);
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
|
||||
import type { AppMessagesManager, Dialog, HistoryResult } from "../../lib/appManagers/appMessagesManager";
|
||||
import type { AppMessagesManager, Dialog, HistoryResult, MyMessage } from "../../lib/appManagers/appMessagesManager";
|
||||
import type { AppSidebarRight } from "../sidebarRight";
|
||||
import type { AppStickersManager } from "../../lib/appManagers/appStickersManager";
|
||||
import type { AppUsersManager } from "../../lib/appManagers/appUsersManager";
|
||||
|
@ -7,16 +7,16 @@ import type { AppInlineBotsManager } from "../../lib/appManagers/appInlineBotsMa
|
|||
import type { AppPhotosManager } from "../../lib/appManagers/appPhotosManager";
|
||||
import type { AppDocsManager } from "../../lib/appManagers/appDocsManager";
|
||||
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
|
||||
import { findUpClassName, cancelEvent, findUpTag, whichChild, getElementByPoint, attachClickEvent } from "../../helpers/dom";
|
||||
import { findUpClassName, cancelEvent, findUpTag, whichChild, getElementByPoint, attachClickEvent, positionElementByIndex } from "../../helpers/dom";
|
||||
import { getObjectKeysAndSort } from "../../helpers/object";
|
||||
import { isTouchSupported } from "../../helpers/touchSupport";
|
||||
import { logger } from "../../lib/logger";
|
||||
import rootScope from "../../lib/rootScope";
|
||||
import AppMediaViewer from "../appMediaViewer";
|
||||
import BubbleGroups from "./bubbleGroups";
|
||||
import PopupDatePicker from "../popupDatepicker";
|
||||
import PopupForward from "../popupForward";
|
||||
import PopupStickers from "../popupStickers";
|
||||
import PopupDatePicker from "../popups/datePicker";
|
||||
import PopupForward from "../popups/forward";
|
||||
import PopupStickers from "../popups/stickers";
|
||||
import ProgressivePreloader from "../preloader";
|
||||
import Scrollable from "../scrollable";
|
||||
import StickyIntersector from "../stickyIntersector";
|
||||
|
@ -35,6 +35,7 @@ import LazyLoadQueue from "../lazyLoadQueue";
|
|||
import { AppChatsManager } from "../../lib/appManagers/appChatsManager";
|
||||
import Chat from "./chat";
|
||||
import ListenerSetter from "../../helpers/listenerSetter";
|
||||
import PollElement from "../poll";
|
||||
|
||||
const IGNORE_ACTIONS = ['messageActionHistoryClear'];
|
||||
|
||||
|
@ -101,7 +102,7 @@ export default class ChatBubbles {
|
|||
|
||||
public replyFollowHistory: number[] = [];
|
||||
|
||||
constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appSidebarRight: AppSidebarRight, private appStickersManager: AppStickersManager, private appUsersManager: AppUsersManager, private appInlineBotsManager: AppInlineBotsManager, private appPhotosManager: AppPhotosManager, private appDocsManager: AppDocsManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager) {
|
||||
constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appStickersManager: AppStickersManager, private appUsersManager: AppUsersManager, private appInlineBotsManager: AppInlineBotsManager, private appPhotosManager: AppPhotosManager, private appDocsManager: AppDocsManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager) {
|
||||
this.chat.log.error('Bubbles construction');
|
||||
|
||||
this.listenerSetter = new ListenerSetter();
|
||||
|
@ -126,32 +127,19 @@ export default class ChatBubbles {
|
|||
|
||||
// * events
|
||||
|
||||
// will call when message is sent (only 1)
|
||||
this.listenerSetter.add(rootScope, 'history_append', (e) => {
|
||||
let details = e.detail;
|
||||
|
||||
if(!this.scrolledAllDown) {
|
||||
this.chat.setPeer(this.peerId, 0);
|
||||
} else {
|
||||
this.renderNewMessagesByIds([details.messageId], true);
|
||||
}
|
||||
});
|
||||
|
||||
// will call when sent for update pos
|
||||
this.listenerSetter.add(rootScope, 'history_update', (e) => {
|
||||
let details = e.detail;
|
||||
const {storage, peerId, mid} = e.detail;
|
||||
|
||||
if(details.mid && details.peerId == this.peerId) {
|
||||
let mid = details.mid;
|
||||
|
||||
let bubble = this.bubbles[mid];
|
||||
if(mid && peerId == this.peerId && this.chat.getMessagesStorage() === storage) {
|
||||
const bubble = this.bubbles[mid];
|
||||
if(!bubble) return;
|
||||
|
||||
let message = this.chat.getMessage(mid);
|
||||
//this.log('history_update', this.bubbles[mid], mid, message);
|
||||
|
||||
let dateMessage = this.getDateContainerByMessage(message, false);
|
||||
dateMessage.container.append(bubble);
|
||||
const message = this.chat.getMessage(mid);
|
||||
//bubble.remove();
|
||||
this.bubbleGroups.removeBubble(bubble, message.mid);
|
||||
this.setBubblePosition(bubble, message, false);
|
||||
//this.log('history_update', this.bubbles[mid], mid, message);
|
||||
|
||||
this.bubbleGroups.addBubble(bubble, message, false);
|
||||
|
||||
|
@ -159,29 +147,6 @@ export default class ChatBubbles {
|
|||
}
|
||||
});
|
||||
|
||||
this.listenerSetter.add(rootScope, 'history_multiappend', (e) => {
|
||||
const msgIdsByPeer = e.detail;
|
||||
|
||||
for(const peerId in msgIdsByPeer) {
|
||||
appSidebarRight.sharedMediaTab.renderNewMessages(+peerId, msgIdsByPeer[peerId]);
|
||||
}
|
||||
|
||||
if(!(this.peerId in msgIdsByPeer)) return;
|
||||
const msgIds = msgIdsByPeer[this.peerId];
|
||||
this.renderNewMessagesByIds(msgIds);
|
||||
});
|
||||
|
||||
this.listenerSetter.add(rootScope, 'history_delete', (e) => {
|
||||
const {peerId, msgs} = e.detail;
|
||||
|
||||
const mids = Object.keys(msgs).map(s => +s);
|
||||
appSidebarRight.sharedMediaTab.deleteDeletedMessages(peerId, mids);
|
||||
|
||||
if(peerId == this.peerId) {
|
||||
this.deleteMessagesByIds(mids);
|
||||
}
|
||||
});
|
||||
|
||||
this.listenerSetter.add(rootScope, 'dialog_flush', (e) => {
|
||||
let peerId: number = e.detail.peerId;
|
||||
if(this.peerId == peerId) {
|
||||
|
@ -191,16 +156,18 @@ export default class ChatBubbles {
|
|||
|
||||
// Calls when message successfully sent and we have an id
|
||||
this.listenerSetter.add(rootScope, 'message_sent', (e) => {
|
||||
const {tempId, mid} = e.detail;
|
||||
const {storage, tempId, tempMessage, mid} = e.detail;
|
||||
|
||||
// ! can't use peerId to validate here, because id can be the same in 'scheduled' and 'chat' types
|
||||
if(this.chat.getMessagesStorage() !== storage) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.log('message_sent', e.detail);
|
||||
|
||||
const message = this.chat.getMessage(mid);
|
||||
|
||||
appSidebarRight.sharedMediaTab.renderNewMessages(message.peerId, [mid]);
|
||||
|
||||
const mounted = this.getMountedBubble(tempId) || this.getMountedBubble(mid);
|
||||
if(mounted) {
|
||||
const message = this.chat.getMessage(mid);
|
||||
const bubble = mounted.bubble;
|
||||
//this.bubbles[mid] = bubble;
|
||||
|
||||
|
@ -214,8 +181,9 @@ export default class ChatBubbles {
|
|||
|
||||
if(message.media?.poll) {
|
||||
const newPoll = message.media.poll;
|
||||
const pollElement = bubble.querySelector('poll-element');
|
||||
const pollElement = bubble.querySelector('poll-element') as PollElement;
|
||||
if(pollElement) {
|
||||
pollElement.message = message;
|
||||
pollElement.setAttribute('poll-id', newPoll.id);
|
||||
pollElement.setAttribute('message-id', '' + mid);
|
||||
}
|
||||
|
@ -261,27 +229,43 @@ export default class ChatBubbles {
|
|||
this.unreadOut.delete(tempId);
|
||||
this.unreadOut.add(mid);
|
||||
}
|
||||
|
||||
// * check timing of scheduled message
|
||||
if(this.chat.type === 'scheduled') {
|
||||
const timestamp = Date.now() / 1000 | 0;
|
||||
const maxTimestamp = tempMessage.date - 10;
|
||||
this.log('scheduled timing:', timestamp, maxTimestamp);
|
||||
if(timestamp >= maxTimestamp) {
|
||||
this.deleteMessagesByIds([mid]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.listenerSetter.add(rootScope, 'message_edit', (e) => {
|
||||
const {peerId, mid} = e.detail;
|
||||
const {storage, peerId, mid} = e.detail;
|
||||
|
||||
if(peerId != this.peerId) return;
|
||||
if(peerId != this.peerId || storage !== this.chat.getMessagesStorage()) return;
|
||||
const mounted = this.getMountedBubble(mid);
|
||||
if(!mounted) return;
|
||||
this.renderMessage(mounted.message, true, false, mounted.bubble, false);
|
||||
|
||||
const updatePosition = this.chat.type === 'scheduled';
|
||||
this.renderMessage(mounted.message, true, false, mounted.bubble, updatePosition);
|
||||
|
||||
if(updatePosition) {
|
||||
this.deleteEmptyDateGroups();
|
||||
}
|
||||
});
|
||||
|
||||
this.listenerSetter.add(rootScope, 'album_edit', (e) => {
|
||||
const {peerId, groupId, deletedMids} = e.detail;
|
||||
|
||||
if(peerId != this.peerId) return;
|
||||
const mids = appMessagesManager.getMidsByAlbum(groupId);
|
||||
const maxId = Math.max(...mids.concat(deletedMids));
|
||||
if(!this.bubbles[maxId]) return;
|
||||
const mids = this.appMessagesManager.getMidsByAlbum(groupId);
|
||||
const renderedId = mids.concat(deletedMids).find(mid => this.bubbles[mid]);
|
||||
if(!renderedId) return;
|
||||
|
||||
const renderMaxId = getObjectKeysAndSort(appMessagesManager.groupedMessagesStorage[groupId], 'asc').pop();
|
||||
this.renderMessage(this.chat.getMessage(renderMaxId), true, false, this.bubbles[maxId], false);
|
||||
const renderMaxId = getObjectKeysAndSort(this.appMessagesManager.groupedMessagesStorage[groupId], 'asc').pop();
|
||||
this.renderMessage(this.chat.getMessage(renderMaxId), true, false, this.bubbles[renderedId], false);
|
||||
});
|
||||
|
||||
this.listenerSetter.add(rootScope, 'messages_downloaded', (e) => {
|
||||
|
@ -321,9 +305,48 @@ export default class ChatBubbles {
|
|||
});
|
||||
|
||||
this.listenerSetter.add(this.bubblesContainer, 'click', this.onBubblesClick/* , {capture: true, passive: false} */);
|
||||
|
||||
this.stickyIntersector = new StickyIntersector(this.scrollable.container, (stuck, target) => {
|
||||
for(const timestamp in this.dateMessages) {
|
||||
const dateMessage = this.dateMessages[timestamp];
|
||||
if(dateMessage.container == target) {
|
||||
dateMessage.div.classList.toggle('is-sticky', stuck);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public constructPeerHelpers() {
|
||||
// will call when message is sent (only 1)
|
||||
this.listenerSetter.add(rootScope, 'history_append', (e) => {
|
||||
let details = e.detail;
|
||||
|
||||
if(!this.scrolledAllDown) {
|
||||
this.chat.setPeer(this.peerId, 0);
|
||||
} else {
|
||||
this.renderNewMessagesByIds([details.messageId], true);
|
||||
}
|
||||
});
|
||||
|
||||
this.listenerSetter.add(rootScope, 'history_multiappend', (e) => {
|
||||
const msgIdsByPeer = e.detail;
|
||||
|
||||
if(!(this.peerId in msgIdsByPeer)) return;
|
||||
const msgIds = msgIdsByPeer[this.peerId];
|
||||
this.renderNewMessagesByIds(msgIds);
|
||||
});
|
||||
|
||||
this.listenerSetter.add(rootScope, 'history_delete', (e) => {
|
||||
const {peerId, msgs} = e.detail;
|
||||
|
||||
const mids = Object.keys(msgs).map(s => +s);
|
||||
|
||||
if(peerId == this.peerId) {
|
||||
this.deleteMessagesByIds(mids);
|
||||
}
|
||||
});
|
||||
|
||||
this.listenerSetter.add(rootScope, 'dialog_unread', (e) => {
|
||||
const info = e.detail;
|
||||
|
||||
|
@ -350,16 +373,6 @@ export default class ChatBubbles {
|
|||
}
|
||||
});
|
||||
|
||||
this.stickyIntersector = new StickyIntersector(this.scrollable.container, (stuck, target) => {
|
||||
for(const timestamp in this.dateMessages) {
|
||||
const dateMessage = this.dateMessages[timestamp];
|
||||
if(dateMessage.container == target) {
|
||||
dateMessage.div.classList.toggle('is-sticky', stuck);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.unreadedObserver = new IntersectionObserver((entries) => {
|
||||
if(this.chat.appImManager.offline) { // ! but you can scroll the page without triggering 'focus', need something now
|
||||
return;
|
||||
|
@ -418,11 +431,24 @@ export default class ChatBubbles {
|
|||
}
|
||||
|
||||
public constructScheduledHelpers() {
|
||||
const onUpdate = () => {
|
||||
this.chat.topbar.setTitle(Object.keys(this.appMessagesManager.getScheduledMessagesStorage(this.peerId)).length);
|
||||
};
|
||||
|
||||
this.listenerSetter.add(rootScope, 'scheduled_new', (e) => {
|
||||
const {peerId, mid} = e.detail;
|
||||
if(peerId !== this.peerId) return;
|
||||
|
||||
this.renderNewMessagesByIds([mid]);
|
||||
onUpdate();
|
||||
});
|
||||
|
||||
this.listenerSetter.add(rootScope, 'scheduled_delete', (e) => {
|
||||
const {peerId, mids} = e.detail;
|
||||
if(peerId !== this.peerId) return;
|
||||
|
||||
this.deleteMessagesByIds(mids);
|
||||
onUpdate();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -653,9 +679,11 @@ export default class ChatBubbles {
|
|||
const group = this.appMessagesManager.groupedMessagesStorage[groupId];
|
||||
for(const mid in group) {
|
||||
if(this.bubbles[mid]) {
|
||||
const maxId = Math.max(...Object.keys(group).map(id => +id)); // * because in scheduled album can be rendered by lowest mid during sending
|
||||
return {
|
||||
bubble: this.bubbles[mid],
|
||||
message: this.chat.getMessage(+mid)
|
||||
mid: +mid,
|
||||
message: this.chat.getMessage(maxId)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -681,7 +709,7 @@ export default class ChatBubbles {
|
|||
const bubble = this.bubbles[mid];
|
||||
if(!bubble) return;
|
||||
|
||||
return {bubble, message};
|
||||
return {bubble, mid, message};
|
||||
}
|
||||
|
||||
private findNextMountedBubbleByMsgId(mid: number) {
|
||||
|
@ -843,14 +871,15 @@ export default class ChatBubbles {
|
|||
this.deleteEmptyDateGroups();
|
||||
}
|
||||
|
||||
public renderNewMessagesByIds(msgIds: number[], scrolledDown = this.scrolledDown) {
|
||||
public renderNewMessagesByIds(mids: number[], scrolledDown = this.scrolledDown) {
|
||||
if(!this.scrolledAllDown) { // seems search active or sliced
|
||||
this.log('seems search is active, skipping render:', msgIds);
|
||||
this.log('seems search is active, skipping render:', mids);
|
||||
return;
|
||||
}
|
||||
|
||||
msgIds.forEach((msgId: number) => {
|
||||
let message = this.chat.getMessage(msgId);
|
||||
|
||||
mids = mids.filter(mid => !this.bubbles[mid]);
|
||||
mids.forEach((mid: number) => {
|
||||
const message = this.chat.getMessage(mid);
|
||||
|
||||
/////////this.log('got new message to append:', message);
|
||||
|
||||
|
@ -910,6 +939,10 @@ export default class ChatBubbles {
|
|||
str += ', ' + date.getFullYear();
|
||||
}
|
||||
}
|
||||
|
||||
if(this.chat.type === 'scheduled') {
|
||||
str = 'Scheduled for ' + str;
|
||||
}
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.className = 'bubble service is-date';
|
||||
|
@ -918,6 +951,15 @@ export default class ChatBubbles {
|
|||
|
||||
const container = document.createElement('div');
|
||||
container.className = 'bubbles-date-group';
|
||||
|
||||
const haveTimestamps = getObjectKeysAndSort(this.dateMessages, 'asc');
|
||||
let i = 0;
|
||||
for(; i < haveTimestamps.length; ++i) {
|
||||
const t = haveTimestamps[i];
|
||||
if(dateTimestamp < t) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
this.dateMessages[dateTimestamp] = {
|
||||
div,
|
||||
|
@ -927,11 +969,13 @@ export default class ChatBubbles {
|
|||
|
||||
container.append(div);
|
||||
|
||||
if(reverse) {
|
||||
positionElementByIndex(container, this.chatInner, i);
|
||||
|
||||
/* if(reverse) {
|
||||
this.chatInner.prepend(container);
|
||||
} else {
|
||||
this.chatInner.append(container);
|
||||
}
|
||||
} */
|
||||
|
||||
if(this.stickyIntersector) {
|
||||
this.stickyIntersector.observeStickyHeaderChanges(container);
|
||||
|
@ -1060,7 +1104,7 @@ export default class ChatBubbles {
|
|||
this.log('setPeer peerId:', this.peerId, dialog, lastMsgId, topMessage);
|
||||
|
||||
// add last message, bc in getHistory will load < max_id
|
||||
const additionMsgId = isJump ? 0 : topMessage;
|
||||
const additionMsgId = isJump || this.chat.type !== 'chat' ? 0 : topMessage;
|
||||
|
||||
/* this.setPeerPromise = null;
|
||||
this.preloader.detach();
|
||||
|
@ -1231,7 +1275,7 @@ export default class ChatBubbles {
|
|||
} else if(el.readyState >= 4) return;
|
||||
} else if(el.complete || !el.src) return;
|
||||
|
||||
let promise = new Promise((resolve, reject) => {
|
||||
let promise = new Promise<void>((resolve, reject) => {
|
||||
let r: () => boolean;
|
||||
let onLoad = () => {
|
||||
clearTimeout(timeout);
|
||||
|
@ -1291,12 +1335,7 @@ export default class ChatBubbles {
|
|||
}
|
||||
|
||||
queue.forEach(({message, bubble, reverse}) => {
|
||||
const dateMessage = this.getDateContainerByMessage(message, reverse);
|
||||
if(reverse) {
|
||||
dateMessage.container.insertBefore(bubble, dateMessage.div.nextSibling);
|
||||
} else {
|
||||
dateMessage.container.append(bubble);
|
||||
}
|
||||
this.setBubblePosition(bubble, message, reverse);
|
||||
});
|
||||
|
||||
//setTimeout(() => {
|
||||
|
@ -1309,6 +1348,44 @@ export default class ChatBubbles {
|
|||
}
|
||||
}
|
||||
|
||||
public setBubblePosition(bubble: HTMLElement, message: any, reverse: boolean) {
|
||||
const dateMessage = this.getDateContainerByMessage(message, reverse);
|
||||
let children = Array.from(dateMessage.container.children).slice(1) as HTMLElement[];
|
||||
let i = 0, foundMidOnSameTimestamp = 0;
|
||||
for(; i < children.length; ++i) {
|
||||
const t = children[i];
|
||||
const timestamp = +t.dataset.timestamp;
|
||||
if(message.date < timestamp) {
|
||||
break;
|
||||
} else if(message.date === timestamp) {
|
||||
foundMidOnSameTimestamp = +t.dataset.mid;
|
||||
}
|
||||
|
||||
if(foundMidOnSameTimestamp && message.mid < foundMidOnSameTimestamp) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// * 1 for date
|
||||
let index = 1 + i;
|
||||
if(bubble.parentElement) { // * if already mounted
|
||||
const currentIndex = whichChild(bubble);
|
||||
if(index > currentIndex) {
|
||||
index -= 1; // * minus for already mounted
|
||||
}
|
||||
}
|
||||
|
||||
positionElementByIndex(bubble, dateMessage.container, index);
|
||||
|
||||
//this.bubbleGroups.updateGroupByMessageId(message.mid);
|
||||
|
||||
/* if(reverse) {
|
||||
dateMessage.container.insertBefore(bubble, dateMessage.div.nextSibling);
|
||||
} else {
|
||||
dateMessage.container.append(bubble);
|
||||
} */
|
||||
}
|
||||
|
||||
// * will change .cleaned in cleanup() and new instance will be created
|
||||
public getMiddleware() {
|
||||
const cleanupObj = this.cleanupObj;
|
||||
|
@ -1321,7 +1398,7 @@ export default class ChatBubbles {
|
|||
public renderMessage(message: any, reverse = false, multipleRender = false, bubble: HTMLDivElement = null, updatePosition = true) {
|
||||
this.log.debug('message to render:', message);
|
||||
//return;
|
||||
const albumMustBeRenderedFull = this.chat.type == 'chat';
|
||||
const albumMustBeRenderedFull = this.chat.type === 'chat' || this.chat.type === 'scheduled';
|
||||
if(message.deleted) return;
|
||||
else if(message.grouped_id && albumMustBeRenderedFull) { // will render only last album's message
|
||||
const storage = this.appMessagesManager.groupedMessagesStorage[message.grouped_id];
|
||||
|
@ -1376,21 +1453,30 @@ export default class ChatBubbles {
|
|||
|
||||
// * Нужно очистить прошлую информацию, полезно если удалить последний элемент из альбома в ПОСЛЕДНЕМ БАББЛЕ ГРУППЫ (видно по аватару)
|
||||
const originalMid = +bubble.dataset.mid;
|
||||
if(+message.mid != originalMid) {
|
||||
const sameMid = +message.mid == originalMid;
|
||||
/* if(updatePosition) {
|
||||
bubble.remove(); // * for positionElementByIndex
|
||||
} */
|
||||
|
||||
if(!sameMid || updatePosition) {
|
||||
this.bubbleGroups.removeBubble(bubble, originalMid);
|
||||
}
|
||||
|
||||
if(!sameMid) {
|
||||
delete this.bubbles[originalMid];
|
||||
|
||||
if(!updatePosition) {
|
||||
this.bubbleGroups.addBubble(bubble, message, reverse);
|
||||
}
|
||||
}
|
||||
|
||||
delete this.bubbles[originalMid];
|
||||
//bubble.innerHTML = '';
|
||||
}
|
||||
|
||||
// ! reset due to album edit or delete item
|
||||
this.bubbles[+message.mid] = bubble;
|
||||
bubble.dataset.mid = message.mid;
|
||||
bubble.dataset.timestamp = message.date;
|
||||
|
||||
if(this.chat.selection.isSelecting) {
|
||||
this.chat.selection.toggleBubbleCheckbox(bubble, true);
|
||||
|
@ -1950,7 +2036,7 @@ export default class ChatBubbles {
|
|||
case 'messageMediaPoll': {
|
||||
bubble.classList.remove('is-message-empty');
|
||||
|
||||
const pollElement = wrapPoll(this.peerId, message.media.poll.id, message.mid);
|
||||
const pollElement = wrapPoll(message);
|
||||
messageDiv.prepend(pollElement);
|
||||
messageDiv.classList.add('poll-message');
|
||||
|
||||
|
@ -2223,7 +2309,7 @@ export default class ChatBubbles {
|
|||
return;
|
||||
}
|
||||
|
||||
this.chat.setPeer(this.peerId, history.messages[0].mid);
|
||||
this.chat.setPeer(this.peerId, (history.messages[0] as MyMessage).mid);
|
||||
//console.log('got history date:', history);
|
||||
});
|
||||
};
|
||||
|
@ -2233,7 +2319,8 @@ export default class ChatBubbles {
|
|||
if(this.chat.type === 'chat') {
|
||||
return this.appMessagesManager.getHistory(this.peerId, maxId, loadCount, backLimit);
|
||||
} else if(this.chat.type === 'pinned') {
|
||||
const promise = this.appMessagesManager.getSearch(this.peerId, '', {_: 'inputMessagesFilterPinned'}, maxId, loadCount, 0, backLimit);
|
||||
const promise = this.appMessagesManager.getSearch(this.peerId, '', {_: 'inputMessagesFilterPinned'}, maxId, loadCount, 0, backLimit)
|
||||
.then(value => ({history: value.history.map(m => m.mid)}));
|
||||
|
||||
/* if(maxId) {
|
||||
promise.then(result => {
|
||||
|
@ -2246,7 +2333,11 @@ export default class ChatBubbles {
|
|||
|
||||
return promise;
|
||||
} else if(this.chat.type === 'scheduled') {
|
||||
return this.appMessagesManager.getScheduledMessages(this.peerId).then(mids => ({history: mids}));
|
||||
return this.appMessagesManager.getScheduledMessages(this.peerId).then(mids => {
|
||||
this.scrolledAll = true;
|
||||
this.scrolledAllDown = true;
|
||||
return {history: mids.slice().reverse()};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2296,8 +2387,8 @@ export default class ChatBubbles {
|
|||
|
||||
let additionMsgIds: number[];
|
||||
if(additionMsgId && !isBackLimit) {
|
||||
const historyStorage = this.appMessagesManager.historiesStorage[peerId];
|
||||
if(historyStorage && historyStorage.history.length < loadCount) {
|
||||
const historyStorage = this.appMessagesManager.getHistoryStorage(peerId);
|
||||
if(historyStorage.history.length < loadCount) {
|
||||
additionMsgIds = historyStorage.history.slice();
|
||||
|
||||
// * filter last album, because we don't know is this the last item
|
||||
|
@ -2448,7 +2539,7 @@ export default class ChatBubbles {
|
|||
// preload more
|
||||
//if(!isFirstMessageRender) {
|
||||
if(this.chat.type === 'chat') {
|
||||
const storage = this.appMessagesManager.historiesStorage[peerId];
|
||||
const storage = this.appMessagesManager.getHistoryStorage(peerId);
|
||||
const isMaxIdInHistory = storage.history.indexOf(maxId) !== -1;
|
||||
if(isMaxIdInHistory) { // * otherwise it is a search or jump
|
||||
setTimeout(() => {
|
||||
|
|
|
@ -43,7 +43,7 @@ export default class Chat extends EventListenerBase<{
|
|||
|
||||
public type: ChatType = 'chat';
|
||||
|
||||
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, public apiManager: ApiManagerProxy) {
|
||||
constructor(public appImManager: AppImManager, public appChatsManager: AppChatsManager, public appDocsManager: AppDocsManager, public appInlineBotsManager: AppInlineBotsManager, public appMessagesManager: AppMessagesManager, public appPeersManager: AppPeersManager, public appPhotosManager: AppPhotosManager, public appProfileManager: AppProfileManager, public appStickersManager: AppStickersManager, public appUsersManager: AppUsersManager, public appWebPagesManager: AppWebPagesManager, public appPollsManager: AppPollsManager, public apiManager: ApiManagerProxy) {
|
||||
super();
|
||||
|
||||
this.container = document.createElement('div');
|
||||
|
@ -65,15 +65,16 @@ export default class Chat extends EventListenerBase<{
|
|||
this.type = type;
|
||||
|
||||
if(this.type === 'scheduled') {
|
||||
this.getMessage = (mid) => this.appMessagesManager.getMessageFromStorage(this.appMessagesManager.getScheduledMessagesStorage(this.peerId), mid);
|
||||
this.getMessagesStorage = () => this.appMessagesManager.getScheduledMessagesStorage(this.peerId);
|
||||
//this.getMessage = (mid) => this.appMessagesManager.getMessageFromStorage(this.appMessagesManager.getScheduledMessagesStorage(this.peerId), mid);
|
||||
}
|
||||
}
|
||||
|
||||
private init() {
|
||||
this.topbar = new ChatTopbar(this, appSidebarRight, this.appMessagesManager, this.appPeersManager, this.appChatsManager);
|
||||
this.bubbles = new ChatBubbles(this, this.appMessagesManager, this.appSidebarRight, this.appStickersManager, this.appUsersManager, this.appInlineBotsManager, this.appPhotosManager, this.appDocsManager, this.appPeersManager, this.appChatsManager);
|
||||
this.bubbles = new ChatBubbles(this, this.appMessagesManager, 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.selection = new ChatSelection(this, this.bubbles, this.input, this.appMessagesManager);
|
||||
this.contextMenu = new ChatContextMenu(this.bubbles.bubblesContainer, this, this.appMessagesManager, this.appChatsManager, this.appPeersManager, this.appPollsManager);
|
||||
|
||||
if(this.type === 'chat') {
|
||||
|
@ -93,6 +94,7 @@ export default class Chat extends EventListenerBase<{
|
|||
this.input.constructPinnedHelpers();
|
||||
} else if(this.type === 'scheduled') {
|
||||
this.bubbles.constructScheduledHelpers();
|
||||
this.input.constructPeerHelpers();
|
||||
}
|
||||
|
||||
this.container.classList.add('type-' + this.type);
|
||||
|
@ -196,14 +198,21 @@ export default class Chat extends EventListenerBase<{
|
|||
|
||||
appSidebarRight.sharedMediaTab.fillProfileElements();
|
||||
|
||||
this.log.setPrefix('CHAT-' + peerId + '-' + this.type);
|
||||
|
||||
rootScope.broadcast('peer_changed', peerId);
|
||||
}
|
||||
|
||||
public getMessagesStorage() {
|
||||
return this.appMessagesManager.getMessagesStorage(this.peerId);
|
||||
}
|
||||
|
||||
public getMessage(mid: number) {
|
||||
return this.appMessagesManager.getMessageByPeer(this.peerId, mid);
|
||||
return this.appMessagesManager.getMessageFromStorage(this.getMessagesStorage(), mid);
|
||||
//return this.appMessagesManager.getMessageByPeer(this.peerId, mid);
|
||||
}
|
||||
|
||||
public getMidsByMid(mid: number) {
|
||||
return this.appMessagesManager.getMidsByMid(this.peerId, mid);
|
||||
return this.appMessagesManager.getMidsByMessage(this.getMessage(mid));
|
||||
}
|
||||
}
|
|
@ -7,11 +7,11 @@ import { isTouchSupported } from "../../helpers/touchSupport";
|
|||
import { attachClickEvent, cancelEvent, cancelSelection, findUpClassName } from "../../helpers/dom";
|
||||
import ButtonMenu, { ButtonMenuItemOptions } from "../buttonMenu";
|
||||
import { attachContextMenuListener, openBtnMenu, positionMenu } from "../misc";
|
||||
import PopupDeleteMessages from "../popupDeleteMessages";
|
||||
import PopupForward from "../popupForward";
|
||||
import PopupPinMessage from "../popupUnpinMessage";
|
||||
import PopupDeleteMessages from "../popups/deleteMessages";
|
||||
import PopupForward from "../popups/forward";
|
||||
import PopupPinMessage from "../popups/unpinMessage";
|
||||
import { copyTextToClipboard } from "../../helpers/clipboard";
|
||||
import { isAppleMobile, isSafari } from "../../helpers/userAgent";
|
||||
import PopupSendNow from "../popups/sendNow";
|
||||
|
||||
export default class ChatContextMenu {
|
||||
private buttons: (ButtonMenuItemOptions & {verify: () => boolean, notDirect?: () => boolean, withSelection?: true})[];
|
||||
|
@ -21,6 +21,7 @@ export default class ChatContextMenu {
|
|||
private isTargetAGroupedItem: boolean;
|
||||
public peerId: number;
|
||||
public mid: number;
|
||||
public message: any;
|
||||
|
||||
constructor(private attachTo: HTMLElement, private chat: Chat, private appMessagesManager: AppMessagesManager, private appChatsManager: AppChatsManager, private appPeersManager: AppPeersManager, private appPollsManager: AppPollsManager) {
|
||||
const onContextMenu = (e: MouseEvent | Touch) => {
|
||||
|
@ -71,6 +72,8 @@ export default class ChatContextMenu {
|
|||
this.mid = mid;
|
||||
}
|
||||
|
||||
this.message = this.chat.getMessage(this.mid);
|
||||
|
||||
this.buttons.forEach(button => {
|
||||
let good: boolean;
|
||||
|
||||
|
@ -138,21 +141,50 @@ export default class ChatContextMenu {
|
|||
|
||||
private init() {
|
||||
this.buttons = [{
|
||||
icon: 'send2',
|
||||
text: 'Send Now',
|
||||
onClick: this.onSendScheduledClick,
|
||||
verify: () => this.chat.type === 'scheduled'
|
||||
}, {
|
||||
icon: 'send2',
|
||||
text: 'Send Now selected',
|
||||
onClick: this.onSendScheduledClick,
|
||||
verify: () => this.chat.type === 'scheduled' && this.chat.selection.selectedMids.has(this.mid) && !this.chat.selection.selectionSendNowBtn.hasAttribute('disabled'),
|
||||
notDirect: () => true,
|
||||
withSelection: true
|
||||
}, {
|
||||
icon: 'schedule',
|
||||
text: 'Reschedule',
|
||||
onClick: () => {
|
||||
this.chat.input.scheduleSending(() => {
|
||||
this.appMessagesManager.editMessage(this.message, this.message.message, {
|
||||
scheduleDate: this.chat.input.scheduleDate,
|
||||
entities: this.message.entities
|
||||
});
|
||||
|
||||
this.chat.input.onMessageSent(false, false);
|
||||
}, new Date(this.message.date * 1000));
|
||||
},
|
||||
verify: () => this.chat.type === 'scheduled'
|
||||
}, {
|
||||
icon: 'reply',
|
||||
text: 'Reply',
|
||||
onClick: this.onReplyClick,
|
||||
verify: () => (this.peerId > 0 || this.appChatsManager.hasRights(-this.peerId, 'send')) && this.mid > 0 && !!this.chat.input.messageInput/* ,
|
||||
verify: () => (this.peerId > 0 || this.appChatsManager.hasRights(-this.peerId, 'send')) &&
|
||||
this.mid > 0 &&
|
||||
!!this.chat.input.messageInput &&
|
||||
this.chat.type !== 'scheduled'/* ,
|
||||
cancelEvent: true */
|
||||
}, {
|
||||
icon: 'edit',
|
||||
text: 'Edit',
|
||||
onClick: this.onEditClick,
|
||||
verify: () => this.appMessagesManager.canEditMessage(this.peerId, this.mid, 'text') && !!this.chat.input.messageInput
|
||||
verify: () => this.appMessagesManager.canEditMessage(this.message, 'text') && !!this.chat.input.messageInput
|
||||
}, {
|
||||
icon: 'copy',
|
||||
text: 'Copy',
|
||||
onClick: this.onCopyClick,
|
||||
verify: () => !!this.chat.getMessage(this.mid).message
|
||||
verify: () => !!this.message.message
|
||||
}, {
|
||||
icon: 'copy',
|
||||
text: 'Copy selected',
|
||||
|
@ -164,25 +196,22 @@ export default class ChatContextMenu {
|
|||
icon: 'pin',
|
||||
text: 'Pin',
|
||||
onClick: this.onPinClick,
|
||||
verify: () => {
|
||||
const message = this.chat.getMessage(this.mid);
|
||||
return this.mid > 0 && message._ != 'messageService' && !message.pFlags.pinned && this.appPeersManager.canPinMessage(this.peerId);
|
||||
}
|
||||
verify: () => this.mid > 0 &&
|
||||
this.message._ != 'messageService' &&
|
||||
!this.message.pFlags.pinned &&
|
||||
this.appPeersManager.canPinMessage(this.peerId) &&
|
||||
this.chat.type !== 'scheduled',
|
||||
}, {
|
||||
icon: 'unpin',
|
||||
text: 'Unpin',
|
||||
onClick: this.onUnpinClick,
|
||||
verify: () => {
|
||||
const message = this.chat.getMessage(this.mid);
|
||||
return message.pFlags.pinned && this.appPeersManager.canPinMessage(this.peerId);
|
||||
}
|
||||
verify: () => this.message.pFlags.pinned && this.appPeersManager.canPinMessage(this.peerId),
|
||||
}, {
|
||||
icon: 'revote',
|
||||
text: 'Revote',
|
||||
onClick: this.onRetractVote,
|
||||
verify: () => {
|
||||
const message = this.chat.getMessage(this.mid);
|
||||
const poll = message.media?.poll as Poll;
|
||||
const poll = this.message.media?.poll as Poll;
|
||||
return poll && poll.chosenIndexes.length && !poll.pFlags.closed && !poll.pFlags.quiz;
|
||||
}/* ,
|
||||
cancelEvent: true */
|
||||
|
@ -191,31 +220,27 @@ export default class ChatContextMenu {
|
|||
text: 'Stop poll',
|
||||
onClick: this.onStopPoll,
|
||||
verify: () => {
|
||||
const message = this.chat.getMessage(this.mid);
|
||||
const poll = message.media?.poll;
|
||||
return this.appMessagesManager.canEditMessage(this.peerId, this.mid, 'poll') && poll && !poll.pFlags.closed && this.mid > 0;
|
||||
const poll = this.message.media?.poll;
|
||||
return this.appMessagesManager.canEditMessage(this.message, 'poll') && poll && !poll.pFlags.closed && this.mid > 0;
|
||||
}/* ,
|
||||
cancelEvent: true */
|
||||
}, {
|
||||
icon: 'forward',
|
||||
text: 'Forward',
|
||||
onClick: this.onForwardClick,
|
||||
verify: () => this.mid > 0
|
||||
verify: () => this.mid > 0 && this.chat.type !== 'scheduled'
|
||||
}, {
|
||||
icon: 'forward',
|
||||
text: 'Forward selected',
|
||||
onClick: this.onForwardClick,
|
||||
verify: () => this.chat.selection.selectedMids.has(this.mid) && !this.chat.selection.selectionForwardBtn.hasAttribute('disabled'),
|
||||
verify: () => this.chat.selection.selectionForwardBtn && this.chat.selection.selectedMids.has(this.mid) && !this.chat.selection.selectionForwardBtn.hasAttribute('disabled'),
|
||||
notDirect: () => true,
|
||||
withSelection: true
|
||||
}, {
|
||||
icon: 'select',
|
||||
text: 'Select',
|
||||
onClick: this.onSelectClick,
|
||||
verify: () => {
|
||||
const message = this.chat.getMessage(this.mid);
|
||||
return !message.action && !this.chat.selection.selectedMids.has(this.mid);
|
||||
},
|
||||
verify: () => !this.message.action && !this.chat.selection.selectedMids.has(this.mid),
|
||||
notDirect: () => true,
|
||||
withSelection: true
|
||||
}, {
|
||||
|
@ -229,7 +254,7 @@ export default class ChatContextMenu {
|
|||
icon: 'delete danger',
|
||||
text: 'Delete',
|
||||
onClick: this.onDeleteClick,
|
||||
verify: () => this.appMessagesManager.canDeleteMessage(this.peerId, this.mid)
|
||||
verify: () => this.appMessagesManager.canDeleteMessage(this.message)
|
||||
}, {
|
||||
icon: 'delete danger',
|
||||
text: 'Delete selected',
|
||||
|
@ -244,6 +269,14 @@ export default class ChatContextMenu {
|
|||
this.chat.container.append(this.element);
|
||||
};
|
||||
|
||||
private onSendScheduledClick = () => {
|
||||
if(this.chat.selection.isSelecting) {
|
||||
this.chat.selection.selectionSendNowBtn.click();
|
||||
} else {
|
||||
new PopupSendNow(this.peerId, this.chat.getMidsByMid(this.mid));
|
||||
}
|
||||
};
|
||||
|
||||
private onReplyClick = () => {
|
||||
const message = this.chat.getMessage(this.mid);
|
||||
const chatInputC = this.chat.input;
|
||||
|
@ -277,11 +310,11 @@ export default class ChatContextMenu {
|
|||
};
|
||||
|
||||
private onRetractVote = () => {
|
||||
this.appPollsManager.sendVote(this.peerId, this.mid, []);
|
||||
this.appPollsManager.sendVote(this.message, []);
|
||||
};
|
||||
|
||||
private onStopPoll = () => {
|
||||
this.appPollsManager.stopPoll(this.peerId, this.mid);
|
||||
this.appPollsManager.stopPoll(this.message);
|
||||
};
|
||||
|
||||
private onForwardClick = () => {
|
||||
|
@ -304,7 +337,7 @@ export default class ChatContextMenu {
|
|||
if(this.chat.selection.isSelecting) {
|
||||
this.chat.selection.selectionDeleteBtn.click();
|
||||
} else {
|
||||
new PopupDeleteMessages(this.peerId, this.isTargetAGroupedItem ? [this.mid] : this.chat.getMidsByMid(this.mid));
|
||||
new PopupDeleteMessages(this.peerId, this.isTargetAGroupedItem ? [this.mid] : this.chat.getMidsByMid(this.mid), this.chat.type);
|
||||
}
|
||||
};
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
import type { AppChatsManager } from '../../lib/appManagers/appChatsManager';
|
||||
import type { AppDocsManager } from "../../lib/appManagers/appDocsManager";
|
||||
import type { AppDocsManager, MyDocument } 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";
|
||||
|
@ -11,12 +11,12 @@ 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 { attachClickEvent, blurActiveElement, cancelEvent, cancelSelection, findUpClassName, getRichValue, getSelectedNodes, isInputEmpty, markdownTags, MarkdownType, placeCaretAtEnd, serializeNodes } from "../../helpers/dom";
|
||||
import ButtonMenu, { ButtonMenuItemOptions } from '../buttonMenu';
|
||||
import { attachClickEvent, blurActiveElement, cancelEvent, cancelSelection, findUpClassName, 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 PopupCreatePoll from "../popups/createPoll";
|
||||
import PopupForward from '../popups/forward';
|
||||
import PopupNewMedia from '../popups/newMedia';
|
||||
import Scrollable from "../scrollable";
|
||||
import { toast } from "../toast";
|
||||
import { wrapReply } from "../wrappers";
|
||||
|
@ -28,7 +28,9 @@ import DivAndCaption from '../divAndCaption';
|
|||
import ButtonMenuToggle from '../buttonMenuToggle';
|
||||
import ListenerSetter from '../../helpers/listenerSetter';
|
||||
import Button from '../button';
|
||||
import { attachContextMenuListener, openBtnMenu } from '../misc';
|
||||
import PopupSchedule from '../popups/schedule';
|
||||
import SendMenu from './sendContextMenu';
|
||||
import rootScope from '../../lib/rootScope';
|
||||
|
||||
const RECORD_MIN_TIME = 500;
|
||||
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
|
||||
|
@ -37,7 +39,8 @@ type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply';
|
|||
|
||||
export default class ChatInput {
|
||||
public pageEl = document.getElementById('page-chats') as HTMLDivElement;
|
||||
public messageInput: HTMLDivElement;
|
||||
public messageInput: HTMLElement;
|
||||
public messageInputField: InputField;
|
||||
public fileInput: HTMLInputElement;
|
||||
public inputMessageContainer: HTMLDivElement;
|
||||
public inputScroll: Scrollable;
|
||||
|
@ -56,8 +59,7 @@ export default class ChatInput {
|
|||
public attachMenu: HTMLButtonElement;
|
||||
private attachMenuButtons: (ButtonMenuItemOptions & {verify: (peerId: number) => boolean})[];
|
||||
|
||||
public sendMenu: HTMLDivElement;
|
||||
private sendMenuButtons: (ButtonMenuItemOptions & {verify: (peerId: number) => boolean})[];
|
||||
public sendMenu: SendMenu;
|
||||
|
||||
public replyElements: {
|
||||
container?: HTMLElement,
|
||||
|
@ -69,9 +71,11 @@ export default class ChatInput {
|
|||
public willSendWebPage: any = null;
|
||||
public forwardingMids: number[] = [];
|
||||
public forwardingFromPeerId: number = 0;
|
||||
public replyToMsgId = 0;
|
||||
public editMsgId = 0;
|
||||
public replyToMsgId: number;
|
||||
public editMsgId: number;
|
||||
public noWebPage: true;
|
||||
public scheduleDate: number;
|
||||
public sendSilent: true;
|
||||
|
||||
private recorder: any;
|
||||
private recording = false;
|
||||
|
@ -160,8 +164,36 @@ export default class ChatInput {
|
|||
|
||||
this.inputScroll = new Scrollable(this.inputMessageContainer);
|
||||
|
||||
this.btnScheduled = ButtonIcon('schedule', {noRipple: true});
|
||||
this.btnScheduled.classList.add('btn-scheduled', 'hide');
|
||||
if(this.chat.type === 'chat') {
|
||||
this.btnScheduled = ButtonIcon('schedule', {noRipple: true});
|
||||
this.btnScheduled.classList.add('btn-scheduled', 'hide');
|
||||
|
||||
attachClickEvent(this.btnScheduled, (e) => {
|
||||
this.appImManager.openScheduled(this.chat.peerId);
|
||||
}, {listenerSetter: this.listenerSetter});
|
||||
|
||||
this.listenerSetter.add(rootScope, 'scheduled_new', (e) => {
|
||||
const peerId = e.detail.peerId;
|
||||
|
||||
if(this.chat.peerId !== peerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.btnScheduled.classList.remove('hide');
|
||||
});
|
||||
|
||||
this.listenerSetter.add(rootScope, 'scheduled_delete', (e) => {
|
||||
const peerId = e.detail.peerId;
|
||||
|
||||
if(this.chat.peerId !== peerId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.appMessagesManager.getScheduledMessages(this.chat.peerId).then(value => {
|
||||
this.btnScheduled.classList.toggle('hide', !value.length);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.attachMenuButtons = [{
|
||||
icon: 'photo',
|
||||
|
@ -187,7 +219,7 @@ export default class ChatInput {
|
|||
icon: 'poll',
|
||||
text: 'Poll',
|
||||
onClick: () => {
|
||||
new PopupCreatePoll(this.chat.peerId).show();
|
||||
new PopupCreatePoll(this.chat).show();
|
||||
},
|
||||
verify: (peerId: number) => peerId < 0 && this.appChatsManager.hasRights(peerId, 'send', 'send_polls')
|
||||
}];
|
||||
|
@ -196,24 +228,6 @@ export default class ChatInput {
|
|||
this.attachMenu.classList.add('attach-file', 'tgico-attach');
|
||||
this.attachMenu.classList.remove('tgico-more');
|
||||
|
||||
this.sendMenuButtons = [{
|
||||
icon: 'mute',
|
||||
text: 'Send Without Sound',
|
||||
onClick: () => {
|
||||
|
||||
},
|
||||
verify: (peerId: number) => true
|
||||
}, {
|
||||
icon: 'schedule',
|
||||
text: 'Schedule Message',
|
||||
onClick: () => {
|
||||
|
||||
},
|
||||
verify: (peerId: number) => true
|
||||
}];
|
||||
|
||||
this.sendMenu = ButtonMenu(this.sendMenuButtons, this.listenerSetter);
|
||||
this.sendMenu.classList.add('menu-send', 'top-left');
|
||||
//this.inputContainer.append(this.sendMenu);
|
||||
|
||||
this.recordTimeEl = document.createElement('div');
|
||||
|
@ -224,7 +238,7 @@ export default class ChatInput {
|
|||
this.fileInput.multiple = true;
|
||||
this.fileInput.style.display = 'none';
|
||||
|
||||
this.newMessageWrapper.append(this.btnToggleEmoticons, this.inputMessageContainer, this.btnScheduled, this.attachMenu, this.recordTimeEl, this.fileInput);
|
||||
this.newMessageWrapper.append(...[this.btnToggleEmoticons, this.inputMessageContainer, this.btnScheduled, this.attachMenu, this.recordTimeEl, this.fileInput].filter(Boolean));
|
||||
|
||||
this.rowsWrapper.append(this.replyElements.container, this.newMessageWrapper);
|
||||
|
||||
|
@ -239,19 +253,32 @@ export default class ChatInput {
|
|||
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-schedule"></span>
|
||||
<span class="tgico tgico-check"></span>
|
||||
<span class="tgico tgico-microphone2"></span>
|
||||
`);
|
||||
|
||||
attachContextMenuListener(this.btnSend, (e: any) => {
|
||||
if(this.isInputEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
cancelEvent(e);
|
||||
openBtnMenu(this.sendMenu);
|
||||
}, this.listenerSetter);
|
||||
this.btnSendContainer.append(this.recordRippleEl, this.btnSend);
|
||||
|
||||
this.btnSendContainer.append(this.recordRippleEl, this.btnSend, this.sendMenu);
|
||||
if(this.chat.type !== 'scheduled') {
|
||||
this.sendMenu = new SendMenu({
|
||||
onSilentClick: () => {
|
||||
this.sendSilent = true;
|
||||
this.sendMessage();
|
||||
},
|
||||
onScheduleClick: () => {
|
||||
this.scheduleSending(undefined);
|
||||
},
|
||||
listenerSetter: this.listenerSetter,
|
||||
openSide: 'top-left',
|
||||
onContextElement: this.btnSend,
|
||||
onOpen: () => {
|
||||
return !this.isInputEmpty();
|
||||
}
|
||||
});
|
||||
|
||||
this.btnSendContainer.append(this.sendMenu.sendMenu);
|
||||
}
|
||||
|
||||
this.inputContainer.append(this.btnCancelRecord, this.btnSendContainer);
|
||||
|
||||
|
@ -294,7 +321,7 @@ export default class ChatInput {
|
|||
return;
|
||||
}
|
||||
|
||||
new PopupNewMedia(Array.from(files).slice(), this.willAttachType);
|
||||
new PopupNewMedia(this.chat, Array.from(files).slice(), this.willAttachType);
|
||||
this.fileInput.value = '';
|
||||
}, false);
|
||||
|
||||
|
@ -314,11 +341,7 @@ export default class ChatInput {
|
|||
cancelEvent(e);
|
||||
console.log(eventName + ', time: ' + (Date.now() - time));
|
||||
}); */
|
||||
attachClickEvent(this.btnSend, this.onBtnSendClick, {listenerSetter: this.listenerSetter});
|
||||
|
||||
attachClickEvent(this.btnScheduled, (e) => {
|
||||
this.appImManager.setInnerPeer(this.chat.peerId, 0, 'scheduled');
|
||||
}, {listenerSetter: this.listenerSetter});
|
||||
attachClickEvent(this.btnSend, this.onBtnSendClick, {listenerSetter: this.listenerSetter, touchMouseDown: true});
|
||||
|
||||
if(this.recorder) {
|
||||
const onCancelRecordClick = (e: Event) => {
|
||||
|
@ -414,6 +437,22 @@ export default class ChatInput {
|
|||
this.btnToggleEmoticons.classList.toggle(toggleClass, false);
|
||||
};
|
||||
|
||||
public scheduleSending = (callback: () => void = this.sendMessage.bind(this, true), initDate = new Date()) => {
|
||||
new PopupSchedule(initDate, (timestamp) => {
|
||||
const minTimestamp = (Date.now() / 1000 | 0) + 10;
|
||||
if(timestamp <= minTimestamp) {
|
||||
timestamp = undefined;
|
||||
}
|
||||
|
||||
this.scheduleDate = timestamp;
|
||||
callback();
|
||||
|
||||
if(this.chat.type !== 'scheduled' && timestamp) {
|
||||
this.appImManager.openScheduled(this.chat.peerId);
|
||||
}
|
||||
}).show();
|
||||
};
|
||||
|
||||
public setUnreadCount() {
|
||||
const dialog = this.appMessagesManager.getDialogByPeerId(this.chat.peerId)[0];
|
||||
const count = dialog?.unread_count;
|
||||
|
@ -457,9 +496,12 @@ export default class ChatInput {
|
|||
this.setUnreadCount();
|
||||
}
|
||||
|
||||
if(this.chat.type == 'pinned') {
|
||||
if(this.chat.type === 'pinned') {
|
||||
this.chatInput.classList.toggle('can-pin', this.appPeersManager.canPinMessage(peerId));
|
||||
} else if(this.chat.type == 'chat') {
|
||||
}/* else if(this.chat.type === 'chat') {
|
||||
} */
|
||||
|
||||
if(this.btnScheduled) {
|
||||
this.btnScheduled.classList.add('hide');
|
||||
const middleware = this.chat.bubbles.getMiddleware();
|
||||
this.appMessagesManager.getScheduledMessages(peerId).then(mids => {
|
||||
|
@ -468,6 +510,10 @@ export default class ChatInput {
|
|||
});
|
||||
}
|
||||
|
||||
if(this.sendMenu) {
|
||||
this.sendMenu.setPeerId(peerId);
|
||||
}
|
||||
|
||||
if(this.messageInput) {
|
||||
const canWrite = this.appMessagesManager.canWriteToPeer(peerId);
|
||||
this.chatInput.classList.add('no-transition');
|
||||
|
@ -495,20 +541,20 @@ export default class ChatInput {
|
|||
}
|
||||
|
||||
private attachMessageInputField() {
|
||||
const messageInputField = InputField({
|
||||
this.messageInputField = new InputField({
|
||||
placeholder: 'Message',
|
||||
name: 'message'
|
||||
});
|
||||
|
||||
messageInputField.input.className = 'input-message-input';
|
||||
this.messageInput = messageInputField.input;
|
||||
this.messageInputField.input.className = 'input-message-input';
|
||||
this.messageInput = this.messageInputField.input;
|
||||
this.attachMessageInputListeners();
|
||||
|
||||
const container = this.inputScroll.container;
|
||||
if(container.firstElementChild) {
|
||||
container.replaceChild(messageInputField.input, container.firstElementChild);
|
||||
container.replaceChild(this.messageInputField.input, container.firstElementChild);
|
||||
} else {
|
||||
container.append(messageInputField.input);
|
||||
container.append(this.messageInputField.input);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -752,7 +798,7 @@ export default class ChatInput {
|
|||
|
||||
//console.log('messageInput input', this.messageInput.innerText, this.serializeNodes(Array.from(this.messageInput.childNodes)));
|
||||
//const value = this.messageInput.innerText;
|
||||
const richValue = getRichValue(this.messageInput);
|
||||
const richValue = this.messageInputField.value;
|
||||
|
||||
//const entities = RichTextProcessor.parseEntities(value);
|
||||
const markdownEntities: MessageEntity[] = [];
|
||||
|
@ -846,8 +892,8 @@ export default class ChatInput {
|
|||
|
||||
private onBtnSendClick = (e: Event) => {
|
||||
cancelEvent(e);
|
||||
|
||||
if(!this.recorder || this.recording || !this.isInputEmpty() || this.forwardingMids.length) {
|
||||
|
||||
if(!this.recorder || this.recording || !this.isInputEmpty() || this.forwardingMids.length || this.editMsgId) {
|
||||
if(this.recording) {
|
||||
if((Date.now() - this.recordStartTime) < RECORD_MIN_TIME) {
|
||||
this.btnCancelRecord.click();
|
||||
|
@ -1011,21 +1057,28 @@ export default class ChatInput {
|
|||
}
|
||||
|
||||
public updateSendBtn() {
|
||||
let icon: 'send' | 'record';
|
||||
let icon: 'send' | 'record' | 'edit' | 'schedule';
|
||||
|
||||
if(!this.recorder || this.recording || !this.isInputEmpty() || this.forwardingMids.length || this.editMsgId) icon = 'send';
|
||||
if(this.editMsgId) icon = 'edit';
|
||||
else if(!this.recorder || this.recording || !this.isInputEmpty() || this.forwardingMids.length) icon = this.chat.type === 'scheduled' ? 'schedule' : 'send';
|
||||
else icon = 'record';
|
||||
|
||||
this.btnSend.classList.toggle('send', icon == 'send');
|
||||
this.btnSend.classList.toggle('record', icon == 'record');
|
||||
['send', 'record', 'edit', 'schedule'].forEach(i => {
|
||||
this.btnSend.classList.toggle(i, icon === i);
|
||||
});
|
||||
}
|
||||
|
||||
public onMessageSent(clearInput = true, clearReply?: boolean) {
|
||||
let dialog = this.appMessagesManager.getDialogByPeerId(this.chat.peerId)[0];
|
||||
if(dialog && dialog.top_message) {
|
||||
this.appMessagesManager.readHistory(this.chat.peerId, dialog.top_message); // lol
|
||||
if(this.chat.type !== 'scheduled') {
|
||||
let dialog = this.appMessagesManager.getDialogByPeerId(this.chat.peerId)[0];
|
||||
if(dialog && dialog.top_message) {
|
||||
this.appMessagesManager.readHistory(this.chat.peerId, dialog.top_message); // lol
|
||||
}
|
||||
}
|
||||
|
||||
this.scheduleDate = undefined;
|
||||
this.sendSilent = undefined;
|
||||
|
||||
if(clearInput) {
|
||||
this.lastUrl = '';
|
||||
delete this.noWebPage;
|
||||
|
@ -1040,23 +1093,30 @@ export default class ChatInput {
|
|||
this.updateSendBtn();
|
||||
}
|
||||
|
||||
public sendMessage() {
|
||||
public sendMessage(force = false) {
|
||||
if(this.chat.type === 'scheduled' && !force && !this.editMsgId) {
|
||||
this.scheduleSending();
|
||||
return;
|
||||
}
|
||||
|
||||
//let str = this.serializeNodes(Array.from(this.messageInput.childNodes));
|
||||
let str = getRichValue(this.messageInput);
|
||||
let str = this.messageInputField.value;
|
||||
|
||||
//console.log('childnode str after:', str/* , getRichValue(this.messageInput) */);
|
||||
|
||||
//return;
|
||||
|
||||
if(this.editMsgId) {
|
||||
this.appMessagesManager.editMessage(this.chat.peerId, this.editMsgId, str, {
|
||||
this.appMessagesManager.editMessage(this.chat.getMessage(this.editMsgId), str, {
|
||||
noWebPage: this.noWebPage
|
||||
});
|
||||
} else {
|
||||
this.appMessagesManager.sendText(this.chat.peerId, str, {
|
||||
replyToMsgId: this.replyToMsgId || this.replyToMsgId,
|
||||
replyToMsgId: this.replyToMsgId,
|
||||
noWebPage: this.noWebPage,
|
||||
webPage: this.willSendWebPage
|
||||
webPage: this.willSendWebPage,
|
||||
scheduleDate: this.scheduleDate,
|
||||
silent: this.sendSilent
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1065,18 +1125,40 @@ export default class ChatInput {
|
|||
const mids = this.forwardingMids.slice();
|
||||
const fromPeerId = this.forwardingFromPeerId;
|
||||
const peerId = this.chat.peerId;
|
||||
const silent = this.sendSilent;
|
||||
const scheduleDate = this.scheduleDate;
|
||||
setTimeout(() => {
|
||||
this.appMessagesManager.forwardMessages(peerId, fromPeerId, mids);
|
||||
this.appMessagesManager.forwardMessages(peerId, fromPeerId, mids, {
|
||||
silent,
|
||||
scheduleDate: scheduleDate
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
this.onMessageSent();
|
||||
}
|
||||
|
||||
public sendMessageWithDocument(document: any) {
|
||||
public sendMessageWithDocument(document: MyDocument | string, force = false) {
|
||||
document = this.appDocsManager.getDoc(document);
|
||||
if(document && document._ != 'documentEmpty') {
|
||||
this.appMessagesManager.sendFile(this.chat.peerId, document, {isMedia: true, replyToMsgId: this.replyToMsgId});
|
||||
|
||||
const flag = document.type === 'sticker' ? 'send_stickers' : (document.type === 'gif' ? 'send_gifs' : 'send_media');
|
||||
if(this.chat.peerId < 0 && !this.appChatsManager.hasRights(this.chat.peerId, 'send', flag)) {
|
||||
toast(POSTING_MEDIA_NOT_ALLOWED);
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.chat.type === 'scheduled' && !force) {
|
||||
this.scheduleSending(() => this.sendMessageWithDocument(document, true));
|
||||
return false;
|
||||
}
|
||||
|
||||
if(document) {
|
||||
this.appMessagesManager.sendFile(this.chat.peerId, document, {
|
||||
isMedia: true,
|
||||
replyToMsgId: this.replyToMsgId,
|
||||
silent: this.sendSilent,
|
||||
scheduleDate: this.scheduleDate
|
||||
});
|
||||
this.onMessageSent(false, true);
|
||||
|
||||
if(document.type == 'sticker') {
|
||||
|
@ -1089,6 +1171,18 @@ export default class ChatInput {
|
|||
return false;
|
||||
}
|
||||
|
||||
/* public sendSomething(callback: () => void, force = false) {
|
||||
if(this.chat.type === 'scheduled' && !force) {
|
||||
this.scheduleSending(() => this.sendSomething(callback, true));
|
||||
return false;
|
||||
}
|
||||
|
||||
callback();
|
||||
this.onMessageSent(false, true);
|
||||
|
||||
return true;
|
||||
} */
|
||||
|
||||
public initMessageEditing(mid: number) {
|
||||
const message = this.chat.getMessage(mid);
|
||||
|
||||
|
@ -1146,10 +1240,10 @@ export default class ChatInput {
|
|||
this.willSendWebPage = null;
|
||||
}
|
||||
|
||||
this.replyToMsgId = 0;
|
||||
this.replyToMsgId = undefined;
|
||||
this.forwardingMids.length = 0;
|
||||
this.forwardingFromPeerId = 0;
|
||||
this.editMsgId = 0;
|
||||
this.editMsgId = undefined;
|
||||
this.helperType = this.helperFunc = undefined;
|
||||
this.chat.container.classList.remove('is-helper-active');
|
||||
}
|
||||
|
|
|
@ -80,6 +80,7 @@ export default class PinnedContainer {
|
|||
}
|
||||
|
||||
public fill(title: string, subtitle: string, message: any) {
|
||||
this.divAndCaption.container.dataset.peerId = '' + message.peerId;
|
||||
this.divAndCaption.container.dataset.mid = '' + message.mid;
|
||||
this.divAndCaption.fill(title, subtitle, message);
|
||||
this.topbar.setUtilsWidth();
|
||||
|
|
|
@ -2,7 +2,7 @@ import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManage
|
|||
import type { AppPeersManager } from "../../lib/appManagers/appPeersManager";
|
||||
import type ChatTopbar from "./topbar";
|
||||
import { ScreenSize } from "../../helpers/mediaSizes";
|
||||
import PopupPinMessage from "../popupUnpinMessage";
|
||||
import PopupPinMessage from "../popups/unpinMessage";
|
||||
import PinnedContainer from "./pinnedContainer";
|
||||
import PinnedMessageBorder from "./pinnedMessageBorder";
|
||||
import ReplyContainer, { wrapReplyDivAndCaption } from "./replyContainer";
|
||||
|
@ -429,7 +429,7 @@ export default class ChatPinnedMessage {
|
|||
|
||||
const result = (await Promise.all(promises))[0];
|
||||
|
||||
let backLimited = result.history.findIndex(_mid => _mid <= mid);
|
||||
let backLimited = result.history.findIndex(message => message.mid <= mid);
|
||||
if(backLimited === -1) {
|
||||
backLimited = result.history.length;
|
||||
}/* else {
|
||||
|
@ -437,7 +437,7 @@ export default class ChatPinnedMessage {
|
|||
} */
|
||||
|
||||
this.offsetIndex = result.offset_id_offset ? result.offset_id_offset - backLimited : 0;
|
||||
this.mids = result.history.slice();
|
||||
this.mids = result.history.map(message => message.mid).slice();
|
||||
this.count = result.count;
|
||||
|
||||
if(!this.count) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import type ChatTopbar from "./topbar";
|
||||
import { cancelEvent, whichChild, findUpTag } from "../../helpers/dom";
|
||||
import AppSearch, { SearchGroup } from "../appSearch";
|
||||
import PopupDatePicker from "../popupDatepicker";
|
||||
import PopupDatePicker from "../popups/datePicker";
|
||||
import { ripple } from "../ripple";
|
||||
import InputSearch from "../inputSearch";
|
||||
import type Chat from "./chat";
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
|
||||
import type ChatBubbles from "./bubbles";
|
||||
import type ChatInput from "./input";
|
||||
import type Chat from "./chat";
|
||||
import { isTouchSupported } from "../../helpers/touchSupport";
|
||||
import { blurActiveElement, cancelEvent, cancelSelection, findUpClassName, getSelectedText } from "../../helpers/dom";
|
||||
import Button from "../button";
|
||||
import ButtonIcon from "../buttonIcon";
|
||||
import CheckboxField from "../checkbox";
|
||||
import PopupDeleteMessages from "../popupDeleteMessages";
|
||||
import PopupForward from "../popupForward";
|
||||
import PopupDeleteMessages from "../popups/deleteMessages";
|
||||
import PopupForward from "../popups/forward";
|
||||
import { toast } from "../toast";
|
||||
import SetTransition from "../singleTransition";
|
||||
import ListenerSetter from "../../helpers/listenerSetter";
|
||||
import PopupSendNow from "../popups/sendNow";
|
||||
|
||||
const MAX_SELECTION_LENGTH = 100;
|
||||
//const MIN_CLICK_MOVE = 32; // minimum bubble height
|
||||
|
@ -21,6 +23,7 @@ export default class ChatSelection {
|
|||
|
||||
private selectionContainer: HTMLElement;
|
||||
private selectionCountEl: HTMLElement;
|
||||
public selectionSendNowBtn: HTMLElement;
|
||||
public selectionForwardBtn: HTMLElement;
|
||||
public selectionDeleteBtn: HTMLElement;
|
||||
|
||||
|
@ -28,7 +31,7 @@ export default class ChatSelection {
|
|||
|
||||
private listenerSetter: ListenerSetter;
|
||||
|
||||
constructor(private bubbles: ChatBubbles, private input: ChatInput, private appMessagesManager: AppMessagesManager) {
|
||||
constructor(private chat: Chat, private bubbles: ChatBubbles, private input: ChatInput, private appMessagesManager: AppMessagesManager) {
|
||||
const bubblesContainer = bubbles.bubblesContainer;
|
||||
this.listenerSetter = bubbles.listenerSetter;
|
||||
|
||||
|
@ -205,7 +208,7 @@ export default class ChatSelection {
|
|||
if(!this.selectedMids.size && !forceSelection) return;
|
||||
this.selectionCountEl.innerText = this.selectedMids.size + ' Message' + (this.selectedMids.size == 1 ? '' : 's');
|
||||
|
||||
let cantForward = !this.selectedMids.size, cantDelete = !this.selectedMids.size;
|
||||
let cantForward = !this.selectedMids.size, cantDelete = !this.selectedMids.size, cantSend = !this.selectedMids.size;
|
||||
for(const mid of this.selectedMids.values()) {
|
||||
const message = this.appMessagesManager.getMessageByPeer(this.bubbles.peerId, mid);
|
||||
if(!cantForward) {
|
||||
|
@ -216,7 +219,7 @@ export default class ChatSelection {
|
|||
|
||||
|
||||
if(!cantDelete) {
|
||||
const canDelete = this.appMessagesManager.canDeleteMessage(this.bubbles.peerId, mid);
|
||||
const canDelete = this.appMessagesManager.canDeleteMessage(this.chat.getMessage(mid));
|
||||
if(!canDelete) {
|
||||
cantDelete = true;
|
||||
}
|
||||
|
@ -225,7 +228,8 @@ export default class ChatSelection {
|
|||
if(cantForward && cantDelete) break;
|
||||
}
|
||||
|
||||
this.selectionForwardBtn.toggleAttribute('disabled', cantForward);
|
||||
this.selectionSendNowBtn && this.selectionSendNowBtn.toggleAttribute('disabled', cantSend);
|
||||
this.selectionForwardBtn && this.selectionForwardBtn.toggleAttribute('disabled', cantForward);
|
||||
this.selectionDeleteBtn.toggleAttribute('disabled', cantDelete);
|
||||
}
|
||||
|
||||
|
@ -270,7 +274,7 @@ export default class ChatSelection {
|
|||
SetTransition(bubblesContainer, 'is-selecting', forwards, 200, () => {
|
||||
if(!this.isSelecting) {
|
||||
this.selectionContainer.remove();
|
||||
this.selectionContainer = this.selectionForwardBtn = this.selectionDeleteBtn = null;
|
||||
this.selectionContainer = this.selectionSendNowBtn = this.selectionForwardBtn = this.selectionDeleteBtn = null;
|
||||
this.selectedText = undefined;
|
||||
}
|
||||
|
||||
|
@ -292,23 +296,35 @@ export default class ChatSelection {
|
|||
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.listenerSetter.add(this.selectionForwardBtn, 'click', () => {
|
||||
new PopupForward(this.bubbles.peerId, [...this.selectedMids], () => {
|
||||
this.cancelSelection();
|
||||
if(this.chat.type === 'scheduled') {
|
||||
this.selectionSendNowBtn = Button('btn-primary btn-transparent selection-container-send', {icon: 'send2'});
|
||||
this.selectionSendNowBtn.append('Send Now');
|
||||
this.listenerSetter.add(this.selectionSendNowBtn, 'click', () => {
|
||||
new PopupSendNow(this.bubbles.peerId, [...this.selectedMids], () => {
|
||||
this.cancelSelection();
|
||||
})
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if(this.chat.type === 'chat' || this.chat.type === 'pinned') {
|
||||
this.selectionForwardBtn = Button('btn-primary btn-transparent selection-container-forward', {icon: 'forward'});
|
||||
this.selectionForwardBtn.append('Forward');
|
||||
this.listenerSetter.add(this.selectionForwardBtn, 'click', () => {
|
||||
new PopupForward(this.bubbles.peerId, [...this.selectedMids], () => {
|
||||
this.cancelSelection();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
this.selectionDeleteBtn = Button('btn-primary btn-transparent danger selection-container-delete', {icon: 'delete'});
|
||||
this.selectionDeleteBtn.append('Delete');
|
||||
this.listenerSetter.add(this.selectionDeleteBtn, 'click', () => {
|
||||
new PopupDeleteMessages(this.bubbles.peerId, [...this.selectedMids], () => {
|
||||
new PopupDeleteMessages(this.bubbles.peerId, [...this.selectedMids], this.chat.type, () => {
|
||||
this.cancelSelection();
|
||||
});
|
||||
});
|
||||
|
||||
this.selectionContainer.append(btnCancel, this.selectionCountEl, this.selectionForwardBtn, this.selectionDeleteBtn);
|
||||
this.selectionContainer.append(...[btnCancel, this.selectionCountEl, this.selectionSendNowBtn, this.selectionForwardBtn, this.selectionDeleteBtn].filter(Boolean));
|
||||
|
||||
this.input.rowsWrapper.append(this.selectionContainer);
|
||||
}
|
||||
|
@ -365,7 +381,7 @@ export default class ChatSelection {
|
|||
}
|
||||
|
||||
public isGroupedMidsSelected(mid: number) {
|
||||
const mids = this.appMessagesManager.getMidsByMid(this.bubbles.peerId, mid);
|
||||
const mids = this.chat.getMidsByMid(mid);
|
||||
const selectedMids = mids.filter(mid => this.selectedMids.has(mid));
|
||||
return mids.length == selectedMids.length;
|
||||
}
|
||||
|
@ -376,7 +392,7 @@ export default class ChatSelection {
|
|||
const isGrouped = bubble.classList.contains('is-grouped');
|
||||
if(isGrouped) {
|
||||
if(!this.isGroupedBubbleSelected(bubble)) {
|
||||
const mids = this.appMessagesManager.getMidsByMid(this.bubbles.peerId, mid);
|
||||
const mids = this.chat.getMidsByMid(mid);
|
||||
mids.forEach(mid => this.selectedMids.delete(mid));
|
||||
}
|
||||
|
||||
|
|
57
src/components/chat/sendContextMenu.ts
Normal file
57
src/components/chat/sendContextMenu.ts
Normal file
|
@ -0,0 +1,57 @@
|
|||
import { cancelEvent } from "../../helpers/dom";
|
||||
import ListenerSetter from "../../helpers/listenerSetter";
|
||||
import rootScope from "../../lib/rootScope";
|
||||
import ButtonMenu, { ButtonMenuItemOptions } from "../buttonMenu";
|
||||
import { attachContextMenuListener, openBtnMenu } from "../misc";
|
||||
|
||||
export default class SendMenu {
|
||||
public sendMenu: HTMLDivElement;
|
||||
private sendMenuButtons: (ButtonMenuItemOptions & {verify: () => boolean})[];
|
||||
private type: 'schedule' | 'reminder';
|
||||
|
||||
constructor(options: {
|
||||
onSilentClick: () => void,
|
||||
onScheduleClick: () => void,
|
||||
listenerSetter?: ListenerSetter,
|
||||
openSide: string,
|
||||
onContextElement: HTMLElement,
|
||||
onOpen?: () => boolean
|
||||
}) {
|
||||
this.sendMenuButtons = [{
|
||||
icon: 'mute',
|
||||
text: 'Send Without Sound',
|
||||
onClick: options.onSilentClick,
|
||||
verify: () => this.type === 'schedule'
|
||||
}, {
|
||||
icon: 'schedule',
|
||||
text: 'Schedule Message',
|
||||
onClick: options.onScheduleClick,
|
||||
verify: () => this.type === 'schedule'
|
||||
}, {
|
||||
icon: 'schedule',
|
||||
text: 'Set a reminder',
|
||||
onClick: options.onScheduleClick,
|
||||
verify: () => this.type === 'reminder'
|
||||
}];
|
||||
|
||||
this.sendMenu = ButtonMenu(this.sendMenuButtons, options.listenerSetter);
|
||||
this.sendMenu.classList.add('menu-send', options.openSide);
|
||||
|
||||
attachContextMenuListener(options.onContextElement, (e: any) => {
|
||||
if(options.onOpen && !options.onOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendMenuButtons.forEach(button => {
|
||||
button.element.classList.toggle('hide', !button.verify());
|
||||
});
|
||||
|
||||
cancelEvent(e);
|
||||
openBtnMenu(this.sendMenu);
|
||||
}, options.listenerSetter);
|
||||
}
|
||||
|
||||
public setPeerId(peerId: number) {
|
||||
this.type = peerId == rootScope.myId ? 'reminder' : 'schedule';
|
||||
}
|
||||
};
|
|
@ -115,19 +115,18 @@ export default class ChatTopbar {
|
|||
mediaSizes.addListener('changeScreen', this.onChangeScreen);
|
||||
|
||||
this.listenerSetter.add(this.container, 'click', (e) => {
|
||||
const pinned: HTMLElement = findUpClassName(e.target, 'pinned-container');
|
||||
if(pinned) {
|
||||
const container: HTMLElement = findUpClassName(e.target, 'pinned-container');
|
||||
if(container) {
|
||||
cancelEvent(e);
|
||||
|
||||
const mid = +pinned.dataset.mid;
|
||||
if(pinned.classList.contains('pinned-message')) {
|
||||
const mid = +container.dataset.mid;
|
||||
const peerId = +container.dataset.peerId;
|
||||
if(container.classList.contains('pinned-message')) {
|
||||
//if(!this.pinnedMessage.locked) {
|
||||
this.pinnedMessage.followPinnedMessage(mid);
|
||||
//}
|
||||
} else {
|
||||
const message = this.appMessagesManager.getMessageByPeer(this.peerId, mid);
|
||||
|
||||
this.chat.appImManager.setInnerPeer(message.peerId, mid);
|
||||
this.chat.appImManager.setInnerPeer(peerId, mid);
|
||||
}
|
||||
} else {
|
||||
this.appSidebarRight.toggleSidebar(true);
|
||||
|
@ -374,7 +373,8 @@ export default class ChatTopbar {
|
|||
public setTitle(count?: number) {
|
||||
let title = '';
|
||||
if(this.chat.type === 'pinned') {
|
||||
title = count === -1 ? 'Pinned Messages' : (count === 1 ? 'Pinned Message' : (count + ' Pinned Messages'));
|
||||
//title = !count ? 'Pinned Messages' : (count === 1 ? 'Pinned Message' : (count + ' Pinned Messages'));
|
||||
title = [count > 1 ? count : false, 'Pinned Messages'].filter(Boolean).join(' ');
|
||||
|
||||
if(count === undefined) {
|
||||
this.appMessagesManager.getSearchCounters(this.peerId, [{_: 'inputMessagesFilterPinned'}]).then(result => {
|
||||
|
@ -394,7 +394,13 @@ export default class ChatTopbar {
|
|||
});
|
||||
}
|
||||
} else if(this.chat.type === 'scheduled') {
|
||||
title = count === -1 ? 'Scheduled Messages' : (count === 1 ? 'Scheduled Message' : (count + ' Scheduled Messages'));
|
||||
if(this.peerId === rootScope.myId) {
|
||||
//title = !count ? 'Reminders' : (count === 1 ? 'Reminder' : (count + ' Reminders'));
|
||||
title = [count > 1 ? count : false, 'Reminders'].filter(Boolean).join(' ');
|
||||
} else {
|
||||
//title = !count ? 'Scheduled Messages' : (count === 1 ? 'Scheduled Message' : (count + ' Scheduled Messages'));
|
||||
title = [count > 1 ? count : false, 'Scheduled Messages'].filter(Boolean).join(' ');
|
||||
}
|
||||
|
||||
if(count === undefined) {
|
||||
this.appMessagesManager.getScheduledMessages(this.peerId).then(mids => {
|
||||
|
|
|
@ -5,8 +5,8 @@ import appPeersManager from "../lib/appManagers/appPeersManager";
|
|||
import rootScope from "../lib/rootScope";
|
||||
import { findUpTag } from "../helpers/dom";
|
||||
import { positionMenu, openBtnMenu } from "./misc";
|
||||
import { PopupButton } from "./popup";
|
||||
import PopupPeer from "./popupPeer";
|
||||
import { PopupButton } from "./popups";
|
||||
import PopupPeer from "./popups/peer";
|
||||
import ButtonMenu, { ButtonMenuItemOptions } from "./buttonMenu";
|
||||
|
||||
export default class DialogsContextMenu {
|
||||
|
|
|
@ -50,84 +50,106 @@ const checkAndSetRTL = (input: HTMLElement) => {
|
|||
input.style.direction = direction;
|
||||
};
|
||||
|
||||
const InputField = (options: {
|
||||
placeholder?: string,
|
||||
label?: string,
|
||||
name?: string,
|
||||
maxLength?: number,
|
||||
showLengthOn?: number,
|
||||
plainText?: true
|
||||
}) => {
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('input-field');
|
||||
class InputField {
|
||||
public container: HTMLElement;
|
||||
public input: HTMLElement;
|
||||
|
||||
if(options.maxLength) {
|
||||
options.showLengthOn = Math.round(options.maxLength / 3);
|
||||
}
|
||||
constructor(private options: {
|
||||
placeholder?: string,
|
||||
label?: string,
|
||||
name?: string,
|
||||
maxLength?: number,
|
||||
showLengthOn?: number,
|
||||
plainText?: true
|
||||
} = {}) {
|
||||
this.container = document.createElement('div');
|
||||
this.container.classList.add('input-field');
|
||||
|
||||
const {placeholder, label, maxLength, showLengthOn, name, plainText} = options;
|
||||
|
||||
let input: HTMLElement;
|
||||
if(!plainText) {
|
||||
if(init) {
|
||||
init();
|
||||
if(options.maxLength) {
|
||||
options.showLengthOn = Math.round(options.maxLength / 3);
|
||||
}
|
||||
|
||||
div.innerHTML = `
|
||||
<div ${placeholder ? `data-placeholder="${placeholder}"` : ''} contenteditable="true" class="input-field-input"></div>
|
||||
${label ? `<label>${label}</label>` : ''}
|
||||
`;
|
||||
const {placeholder, label, maxLength, showLengthOn, name, plainText} = options;
|
||||
|
||||
input = div.firstElementChild as HTMLElement;
|
||||
const observer = new MutationObserver(() => {
|
||||
checkAndSetRTL(input);
|
||||
|
||||
if(processInput) {
|
||||
processInput();
|
||||
let input: HTMLElement;
|
||||
if(!plainText) {
|
||||
if(init) {
|
||||
init();
|
||||
}
|
||||
});
|
||||
|
||||
// ! childList for paste first symbol
|
||||
observer.observe(input, {characterData: true, childList: true, subtree: true});
|
||||
} else {
|
||||
div.innerHTML = `
|
||||
<input type="text" ${name ? `name="${name}"` : ''} ${placeholder ? `placeholder="${placeholder}"` : ''} autocomplete="off" ${label ? 'required=""' : ''} class="input-field-input">
|
||||
${label ? `<label>${label}</label>` : ''}
|
||||
`;
|
||||
|
||||
input = div.firstElementChild as HTMLElement;
|
||||
input.addEventListener('input', () => checkAndSetRTL(input));
|
||||
this.container.innerHTML = `
|
||||
<div ${placeholder ? `data-placeholder="${placeholder}"` : ''} contenteditable="true" class="input-field-input"></div>
|
||||
${label ? `<label>${label}</label>` : ''}
|
||||
`;
|
||||
|
||||
input = this.container.firstElementChild as HTMLElement;
|
||||
const observer = new MutationObserver(() => {
|
||||
checkAndSetRTL(input);
|
||||
|
||||
if(processInput) {
|
||||
processInput();
|
||||
}
|
||||
});
|
||||
|
||||
// ! childList for paste first symbol
|
||||
observer.observe(input, {characterData: true, childList: true, subtree: true});
|
||||
} else {
|
||||
this.container.innerHTML = `
|
||||
<input type="text" ${name ? `name="${name}"` : ''} ${placeholder ? `placeholder="${placeholder}"` : ''} autocomplete="off" ${label ? 'required=""' : ''} class="input-field-input">
|
||||
${label ? `<label>${label}</label>` : ''}
|
||||
`;
|
||||
|
||||
input = this.container.firstElementChild as HTMLElement;
|
||||
input.addEventListener('input', () => checkAndSetRTL(input));
|
||||
}
|
||||
|
||||
let processInput: () => void;
|
||||
if(maxLength) {
|
||||
const labelEl = this.container.lastElementChild as HTMLLabelElement;
|
||||
let showingLength = false;
|
||||
|
||||
processInput = () => {
|
||||
const wasError = input.classList.contains('error');
|
||||
// * https://stackoverflow.com/a/54369605 #2 to count emoji as 1 symbol
|
||||
const inputLength = plainText ? (input as HTMLInputElement).value.length : [...getRichValue(input)].length;
|
||||
const diff = maxLength - inputLength;
|
||||
const isError = diff < 0;
|
||||
input.classList.toggle('error', isError);
|
||||
|
||||
if(isError || diff <= showLengthOn) {
|
||||
labelEl.innerText = label + ` (${maxLength - inputLength})`;
|
||||
if(!showingLength) showingLength = true;
|
||||
} else if((wasError && !isError) || showingLength) {
|
||||
labelEl.innerText = label;
|
||||
showingLength = false;
|
||||
}
|
||||
};
|
||||
|
||||
input.addEventListener('input', processInput);
|
||||
}
|
||||
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
let processInput: () => void;
|
||||
if(maxLength) {
|
||||
const labelEl = div.lastElementChild as HTMLLabelElement;
|
||||
let showingLength = false;
|
||||
|
||||
processInput = () => {
|
||||
const wasError = input.classList.contains('error');
|
||||
// * https://stackoverflow.com/a/54369605 #2 to count emoji as 1 symbol
|
||||
const inputLength = plainText ? (input as HTMLInputElement).value.length : [...getRichValue(input)].length;
|
||||
const diff = maxLength - inputLength;
|
||||
const isError = diff < 0;
|
||||
input.classList.toggle('error', isError);
|
||||
|
||||
if(isError || diff <= showLengthOn) {
|
||||
labelEl.innerText = label + ` (${maxLength - inputLength})`;
|
||||
if(!showingLength) showingLength = true;
|
||||
} else if((wasError && !isError) || showingLength) {
|
||||
labelEl.innerText = label;
|
||||
showingLength = false;
|
||||
}
|
||||
};
|
||||
|
||||
input.addEventListener('input', processInput);
|
||||
get value() {
|
||||
return this.options.plainText ? (this.input as HTMLInputElement).value : getRichValue(this.input);
|
||||
//return getRichValue(this.input);
|
||||
}
|
||||
|
||||
return {
|
||||
container: div,
|
||||
input: div.firstElementChild as HTMLInputElement
|
||||
};
|
||||
};
|
||||
set value(value: string) {
|
||||
this.setValueSilently(value);
|
||||
|
||||
const event = new Event('input', {bubbles: true, cancelable: true});
|
||||
this.input.dispatchEvent(event);
|
||||
}
|
||||
|
||||
public setValueSilently(value: string) {
|
||||
if(this.options.plainText) {
|
||||
(this.input as HTMLInputElement).value = value;
|
||||
} else {
|
||||
this.input.innerHTML = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default InputField;
|
|
@ -3,7 +3,8 @@ import InputField from "./inputField";
|
|||
|
||||
export default class InputSearch {
|
||||
public container: HTMLElement;
|
||||
public input: HTMLInputElement;
|
||||
public input: HTMLElement;
|
||||
public inputField: InputField;
|
||||
public clearBtn: HTMLElement;
|
||||
|
||||
public prevValue = '';
|
||||
|
@ -11,18 +12,18 @@ export default class InputSearch {
|
|||
public onChange: (value: string) => void;
|
||||
|
||||
constructor(placeholder: string, onChange?: (value: string) => void) {
|
||||
const inputField = InputField({
|
||||
this.inputField = new InputField({
|
||||
placeholder,
|
||||
plainText: true
|
||||
});
|
||||
|
||||
this.container = inputField.container;
|
||||
this.container = this.inputField.container;
|
||||
this.container.classList.remove('input-field');
|
||||
this.container.classList.add('input-search');
|
||||
|
||||
this.onChange = onChange;
|
||||
|
||||
this.input = inputField.input;
|
||||
this.input = this.inputField.input;
|
||||
this.input.classList.add('input-search-input');
|
||||
|
||||
const searchIcon = document.createElement('span');
|
||||
|
@ -59,18 +60,13 @@ export default class InputSearch {
|
|||
};
|
||||
|
||||
get value() {
|
||||
return this.input.value;
|
||||
//return getRichValue(this.input);
|
||||
return this.inputField.value;
|
||||
}
|
||||
|
||||
set value(value: string) {
|
||||
//this.input.innerHTML = value;
|
||||
this.input.value = value;
|
||||
this.prevValue = value;
|
||||
clearTimeout(this.timeout);
|
||||
|
||||
const event = new Event('input', {bubbles: true, cancelable: true});
|
||||
this.input.dispatchEvent(event);
|
||||
this.inputField.value = value;
|
||||
}
|
||||
|
||||
public remove() {
|
||||
|
|
|
@ -64,7 +64,7 @@ export const roundPercents = (percents: number[]) => {
|
|||
//console.log('roundPercents after percents:', percents);
|
||||
};
|
||||
|
||||
const connectedPolls: {id: string, element: PollElement}[] = [];
|
||||
/* const connectedPolls: {id: string, element: PollElement}[] = [];
|
||||
rootScope.on('poll_update', (e) => {
|
||||
const {poll, results} = e.detail as {poll: Poll, results: PollResults};
|
||||
|
||||
|
@ -76,6 +76,17 @@ rootScope.on('poll_update', (e) => {
|
|||
pollElement.performResults(results, poll.chosenIndexes);
|
||||
}
|
||||
}
|
||||
}); */
|
||||
|
||||
rootScope.on('poll_update', (e) => {
|
||||
const {poll, results} = e.detail as {poll: Poll, results: PollResults};
|
||||
|
||||
const pollElement = document.querySelector(`poll-element[poll-id="${poll.id}"]`) as PollElement;
|
||||
//console.log('poll_update', poll, results);
|
||||
if(pollElement) {
|
||||
pollElement.isClosed = !!poll.pFlags.closed;
|
||||
pollElement.performResults(results, poll.chosenIndexes);
|
||||
}
|
||||
});
|
||||
|
||||
rootScope.on('peer_changed', () => {
|
||||
|
@ -152,9 +163,7 @@ export default class PollElement extends HTMLElement {
|
|||
private chosenIndexes: number[] = [];
|
||||
private percents: number[];
|
||||
|
||||
private peerId: number;
|
||||
private pollId: string;
|
||||
private mid: number;
|
||||
public message: any;
|
||||
|
||||
private quizInterval: number;
|
||||
private quizTimer: SVGSVGElement;
|
||||
|
@ -179,12 +188,14 @@ export default class PollElement extends HTMLElement {
|
|||
//console.log('line total length:', lineTotalLength);
|
||||
}
|
||||
|
||||
this.peerId = +this.getAttribute('peer-id');
|
||||
this.pollId = this.getAttribute('poll-id');
|
||||
this.mid = +this.getAttribute('message-id');
|
||||
const {poll, results} = appPollsManager.getPoll(this.pollId);
|
||||
const pollId = this.message.media.poll.id;
|
||||
const {poll, results} = appPollsManager.getPoll(pollId);
|
||||
|
||||
connectedPolls.push({id: this.pollId, element: this});
|
||||
/* const timestamp = Date.now() / 1000 | 0;
|
||||
if(timestamp < this.message.date) { */
|
||||
if(this.message.pFlags.is_scheduled) {
|
||||
this.classList.add('disable-hover');
|
||||
}
|
||||
|
||||
//console.log('pollElement poll:', poll, results);
|
||||
|
||||
|
@ -309,7 +320,7 @@ export default class PollElement extends HTMLElement {
|
|||
|
||||
setTimeout(() => {
|
||||
// нужно запросить апдейт чтобы опрос обновился
|
||||
appPollsManager.getResults(this.peerId, this.mid);
|
||||
appPollsManager.getResults(this.message);
|
||||
}, 3e3);
|
||||
}
|
||||
}, 1e3);
|
||||
|
@ -326,7 +337,7 @@ export default class PollElement extends HTMLElement {
|
|||
|
||||
this.viewResults.addEventListener('click', (e) => {
|
||||
cancelEvent(e);
|
||||
appSidebarRight.pollResultsTab.init(this.peerId, this.pollId, this.mid);
|
||||
appSidebarRight.pollResultsTab.init(this.message);
|
||||
});
|
||||
ripple(this.viewResults);
|
||||
|
||||
|
@ -370,32 +381,6 @@ export default class PollElement extends HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
disconnectedCallback() {
|
||||
// браузер вызывает этот метод при удалении элемента из документа
|
||||
// (может вызываться много раз, если элемент многократно добавляется/удаляется)
|
||||
|
||||
connectedPolls.findAndSplice(c => c.element == this);
|
||||
}
|
||||
|
||||
static get observedAttributes(): string[] {
|
||||
return ['poll-id', 'message-id'/* массив имён атрибутов для отслеживания их изменений */];
|
||||
}
|
||||
|
||||
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
|
||||
// вызывается при изменении одного из перечисленных выше атрибутов
|
||||
// console.log('Poll: attributeChangedCallback', name, oldValue, newValue, this.isConnected);
|
||||
if(name == 'poll-id') {
|
||||
this.pollId = newValue;
|
||||
} else if(name == 'message-id') {
|
||||
this.mid = +newValue;
|
||||
}
|
||||
|
||||
if(this.mid > 0 && oldValue !== undefined && +oldValue < 0) {
|
||||
this.disconnectedCallback();
|
||||
connectedPolls.push({id: this.pollId, element: this});
|
||||
}
|
||||
}
|
||||
|
||||
adoptedCallback() {
|
||||
// вызывается, когда элемент перемещается в новый документ
|
||||
// (происходит в document.adoptNode, используется очень редко)
|
||||
|
@ -466,7 +451,7 @@ export default class PollElement extends HTMLElement {
|
|||
|
||||
this.classList.add('disable-hover');
|
||||
this.sentVote = true;
|
||||
return this.sendVotePromise = appPollsManager.sendVote(this.peerId, this.mid, indexes).then(() => {
|
||||
return this.sendVotePromise = appPollsManager.sendVote(this.message, indexes).then(() => {
|
||||
targets.forEach(target => {
|
||||
target.classList.remove('is-voting');
|
||||
});
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import appDownloadManager from "../lib/appManagers/appDownloadManager";
|
||||
import resizeableImage from "../lib/cropper";
|
||||
import { PopupElement } from "./popup";
|
||||
import { ripple } from "./ripple";
|
||||
import appDownloadManager from "../../lib/appManagers/appDownloadManager";
|
||||
import resizeableImage from "../../lib/cropper";
|
||||
import PopupElement from ".";
|
||||
import { ripple } from "../ripple";
|
||||
|
||||
export default class PopupAvatar extends PopupElement {
|
||||
private cropContainer: HTMLElement;
|
||||
|
@ -26,7 +26,7 @@ export default class PopupAvatar extends PopupElement {
|
|||
this.h6 = document.createElement('h6');
|
||||
this.h6.innerText = 'Drag to Reposition';
|
||||
|
||||
this.closeBtn.classList.remove('btn-icon');
|
||||
this.btnClose.classList.remove('btn-icon');
|
||||
|
||||
this.header.append(this.h6);
|
||||
|
||||
|
@ -70,7 +70,7 @@ export default class PopupAvatar extends PopupElement {
|
|||
ripple(this.btnSubmit);
|
||||
this.btnSubmit.addEventListener('click', () => {
|
||||
this.cropper.crop();
|
||||
this.closeBtn.click();
|
||||
this.btnClose.click();
|
||||
|
||||
this.canvas.toBlob(blob => {
|
||||
this.blob = blob; // save blob to send after reg
|
|
@ -1,20 +1,20 @@
|
|||
import appMessagesManager from "../lib/appManagers/appMessagesManager";
|
||||
import appPeersManager from "../lib/appManagers/appPeersManager";
|
||||
import appPollsManager, { Poll } from "../lib/appManagers/appPollsManager";
|
||||
import { cancelEvent, findUpTag, getRichValue, isInputEmpty, whichChild } from "../helpers/dom";
|
||||
import CheckboxField from "./checkbox";
|
||||
import InputField from "./inputField";
|
||||
import { PopupElement } from "./popup";
|
||||
import RadioField from "./radioField";
|
||||
import Scrollable from "./scrollable";
|
||||
import { toast } from "./toast";
|
||||
import type { Poll } from "../../lib/appManagers/appPollsManager";
|
||||
import type Chat from "../chat/chat";
|
||||
import PopupElement from ".";
|
||||
import { cancelEvent, findUpTag, getRichValue, isInputEmpty, whichChild } from "../../helpers/dom";
|
||||
import CheckboxField from "../checkbox";
|
||||
import InputField from "../inputField";
|
||||
import RadioField from "../radioField";
|
||||
import Scrollable from "../scrollable";
|
||||
import { toast } from "../toast";
|
||||
import SendContextMenu from "../chat/sendContextMenu";
|
||||
|
||||
const MAX_LENGTH_QUESTION = 255;
|
||||
const MAX_LENGTH_OPTION = 100;
|
||||
const MAX_LENGTH_SOLUTION = 200;
|
||||
|
||||
export default class PopupCreatePoll extends PopupElement {
|
||||
private questionInput: HTMLInputElement;
|
||||
private questionInputField: InputField;
|
||||
private questions: HTMLElement;
|
||||
private scrollable: Scrollable;
|
||||
private tempId = 0;
|
||||
|
@ -24,22 +24,41 @@ export default class PopupCreatePoll extends PopupElement {
|
|||
private quizCheckboxField: PopupCreatePoll['anonymousCheckboxField'];
|
||||
|
||||
private correctAnswers: Uint8Array[];
|
||||
private quizSolutionInput: HTMLInputElement;
|
||||
private quizSolutionField: InputField;
|
||||
|
||||
constructor(private peerId: number) {
|
||||
constructor(private chat: Chat) {
|
||||
super('popup-create-poll popup-new-media', null, {closable: true, withConfirm: 'CREATE', body: true});
|
||||
|
||||
this.title.innerText = 'New Poll';
|
||||
|
||||
const questionField = InputField({
|
||||
this.questionInputField = new InputField({
|
||||
placeholder: 'Ask a Question',
|
||||
label: 'Ask a Question',
|
||||
name: 'question',
|
||||
maxLength: MAX_LENGTH_QUESTION
|
||||
});
|
||||
this.questionInput = questionField.input;
|
||||
|
||||
this.header.append(questionField.container);
|
||||
if(this.chat.type !== 'scheduled') {
|
||||
const sendMenu = new SendContextMenu({
|
||||
onSilentClick: () => {
|
||||
this.chat.input.sendSilent = true;
|
||||
this.send();
|
||||
},
|
||||
onScheduleClick: () => {
|
||||
this.chat.input.scheduleSending(() => {
|
||||
this.send();
|
||||
});
|
||||
},
|
||||
openSide: 'bottom-left',
|
||||
onContextElement: this.btnConfirm,
|
||||
});
|
||||
|
||||
sendMenu.setPeerId(this.chat.peerId);
|
||||
|
||||
this.header.append(sendMenu.sendMenu);
|
||||
}
|
||||
|
||||
this.header.append(this.questionInputField.container);
|
||||
|
||||
const hr = document.createElement('hr');
|
||||
const d = document.createElement('div');
|
||||
|
@ -56,7 +75,7 @@ export default class PopupCreatePoll extends PopupElement {
|
|||
settingsCaption.classList.add('caption');
|
||||
settingsCaption.innerText = 'Settings';
|
||||
|
||||
if(!appPeersManager.isBroadcast(peerId)) {
|
||||
if(!this.chat.appPeersManager.isBroadcast(this.chat.peerId)) {
|
||||
this.anonymousCheckboxField = CheckboxField('Anonymous Voting', 'anonymous');
|
||||
this.anonymousCheckboxField.input.checked = true;
|
||||
dd.append(this.anonymousCheckboxField.label);
|
||||
|
@ -95,19 +114,18 @@ export default class PopupCreatePoll extends PopupElement {
|
|||
const quizSolutionContainer = document.createElement('div');
|
||||
quizSolutionContainer.classList.add('poll-create-questions');
|
||||
|
||||
const quizSolutionField = InputField({
|
||||
this.quizSolutionField = new InputField({
|
||||
placeholder: 'Add a Comment (Optional)',
|
||||
label: 'Add a Comment (Optional)',
|
||||
name: 'solution',
|
||||
maxLength: MAX_LENGTH_SOLUTION
|
||||
});
|
||||
this.quizSolutionInput = quizSolutionField.input;
|
||||
|
||||
const quizSolutionSubtitle = document.createElement('div');
|
||||
quizSolutionSubtitle.classList.add('subtitle');
|
||||
quizSolutionSubtitle.innerText = 'Users will see this comment after choosing a wrong answer, good for educational purposes.';
|
||||
|
||||
quizSolutionContainer.append(quizSolutionField.container, quizSolutionSubtitle);
|
||||
quizSolutionContainer.append(this.quizSolutionField.container, quizSolutionSubtitle);
|
||||
|
||||
quizElements.push(quizHr, quizSolutionCaption, quizSolutionContainer);
|
||||
quizElements.forEach(el => el.classList.add('hide'));
|
||||
|
@ -115,7 +133,7 @@ export default class PopupCreatePoll extends PopupElement {
|
|||
this.body.parentElement.insertBefore(hr, this.body);
|
||||
this.body.append(d, this.questions, document.createElement('hr'), settingsCaption, dd, ...quizElements);
|
||||
|
||||
this.confirmBtn.addEventListener('click', this.onSubmitClick);
|
||||
this.btnConfirm.addEventListener('click', this.onSubmitClick);
|
||||
|
||||
this.scrollable = new Scrollable(this.body);
|
||||
this.appendMoreField();
|
||||
|
@ -128,14 +146,18 @@ export default class PopupCreatePoll extends PopupElement {
|
|||
private getFilledAnswers() {
|
||||
const answers = Array.from(this.questions.children).map((el, idx) => {
|
||||
const input = el.querySelector('.input-field-input') as HTMLElement;
|
||||
return getRichValue(input);
|
||||
return input instanceof HTMLInputElement ? input.value : getRichValue(input);
|
||||
}).filter(v => !!v.trim());
|
||||
|
||||
return answers;
|
||||
}
|
||||
|
||||
onSubmitClick = (e: MouseEvent) => {
|
||||
const question = getRichValue(this.questionInput);
|
||||
private onSubmitClick = () => {
|
||||
this.send();
|
||||
};
|
||||
|
||||
public send(force = false) {
|
||||
const question = this.questionInputField.value;
|
||||
|
||||
if(!question) {
|
||||
toast('Please enter a question.');
|
||||
|
@ -165,14 +187,22 @@ export default class PopupCreatePoll extends PopupElement {
|
|||
return;
|
||||
}
|
||||
|
||||
const quizSolution = getRichValue(this.quizSolutionInput) || undefined;
|
||||
const quizSolution = this.quizSolutionField.value || undefined;
|
||||
if(quizSolution?.length > MAX_LENGTH_SOLUTION) {
|
||||
toast('Explanation is too long.');
|
||||
return;
|
||||
}
|
||||
|
||||
this.closeBtn.click();
|
||||
this.confirmBtn.removeEventListener('click', this.onSubmitClick);
|
||||
if(this.chat.type === 'scheduled' && !force) {
|
||||
this.chat.input.scheduleSending(() => {
|
||||
this.send(true);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.btnClose.click();
|
||||
this.btnConfirm.removeEventListener('click', this.onSubmitClick);
|
||||
|
||||
//const randomID = [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)];
|
||||
//const randomIDS = bigint(randomID[0]).shiftLeft(32).add(bigint(randomID[1])).toString();
|
||||
|
@ -206,12 +236,17 @@ export default class PopupCreatePoll extends PopupElement {
|
|||
};
|
||||
//poll.id = randomIDS;
|
||||
|
||||
const inputMediaPoll = appPollsManager.getInputMediaPoll(poll, this.correctAnswers, quizSolution);
|
||||
const inputMediaPoll = this.chat.appPollsManager.getInputMediaPoll(poll, this.correctAnswers, quizSolution);
|
||||
|
||||
//console.log('Will try to create poll:', inputMediaPoll);
|
||||
|
||||
appMessagesManager.sendOther(this.peerId, inputMediaPoll);
|
||||
};
|
||||
this.chat.appMessagesManager.sendOther(this.chat.peerId, inputMediaPoll, {
|
||||
scheduleDate: this.chat.input.scheduleDate,
|
||||
silent: this.chat.input.sendSilent
|
||||
});
|
||||
|
||||
this.chat.input.onMessageSent(false, false);
|
||||
}
|
||||
|
||||
onInput = (e: Event) => {
|
||||
const target = e.target as HTMLInputElement;
|
||||
|
@ -243,7 +278,7 @@ export default class PopupCreatePoll extends PopupElement {
|
|||
private appendMoreField() {
|
||||
const tempId = this.tempId++;
|
||||
const idx = this.questions.childElementCount + 1;
|
||||
const questionField = InputField({
|
||||
const questionField = new InputField({
|
||||
placeholder: 'Add an Option',
|
||||
label: 'Option ' + idx,
|
||||
name: 'question-' + tempId,
|
|
@ -1,24 +1,36 @@
|
|||
import { PopupElement } from "./popup";
|
||||
import PopupElement, { PopupOptions } from ".";
|
||||
import { getFullDate, months } from "../../helpers/date";
|
||||
import InputField from "../inputField";
|
||||
|
||||
export default class PopupDatePicker extends PopupElement {
|
||||
private controlsDiv: HTMLElement;
|
||||
private monthTitle: HTMLElement;
|
||||
private prevBtn: HTMLElement;
|
||||
private nextBtn: HTMLElement;
|
||||
protected controlsDiv: HTMLElement;
|
||||
protected monthTitle: HTMLElement;
|
||||
protected prevBtn: HTMLElement;
|
||||
protected nextBtn: HTMLElement;
|
||||
|
||||
private monthsContainer: HTMLElement;
|
||||
private month: HTMLElement;
|
||||
protected monthsContainer: HTMLElement;
|
||||
protected month: HTMLElement;
|
||||
|
||||
private minMonth: Date;
|
||||
private maxMonth: Date;
|
||||
private minDate = new Date('2013-08-01T00:00:00');
|
||||
private maxDate: Date;
|
||||
private selectedDate: Date;
|
||||
private selectedMonth: Date;
|
||||
private selectedEl: HTMLElement;
|
||||
protected minMonth: Date;
|
||||
protected maxMonth: Date;
|
||||
protected minDate: Date;
|
||||
protected maxDate: Date;
|
||||
protected selectedDate: Date;
|
||||
protected selectedMonth: Date;
|
||||
protected selectedEl: HTMLElement;
|
||||
|
||||
constructor(initDate: Date, public onPick: (timestamp: number) => void) {
|
||||
super('popup-date-picker', [{
|
||||
protected timeDiv: HTMLDivElement;
|
||||
protected hoursInputField: InputField;
|
||||
protected minutesInputField: InputField;
|
||||
|
||||
constructor(initDate: Date, public onPick: (timestamp: number) => void, protected options: Partial<{
|
||||
noButtons: true,
|
||||
noTitle: true,
|
||||
minDate: Date,
|
||||
maxDate: Date
|
||||
withTime: true
|
||||
}> & PopupOptions = {}) {
|
||||
super('popup-date-picker', options.noButtons ? [] : [{
|
||||
text: 'CANCEL',
|
||||
isCancel: true
|
||||
}, {
|
||||
|
@ -28,10 +40,9 @@ export default class PopupDatePicker extends PopupElement {
|
|||
this.onPick(this.selectedDate.getTime() / 1000 | 0);
|
||||
}
|
||||
}
|
||||
}]);
|
||||
}], {body: true, ...options});
|
||||
|
||||
const popupBody = document.createElement('div');
|
||||
popupBody.classList.add('popup-body');
|
||||
this.minDate = options.minDate || new Date('2013-08-01T00:00:00');
|
||||
|
||||
// Controls
|
||||
this.controlsDiv = document.createElement('div');
|
||||
|
@ -55,8 +66,80 @@ export default class PopupDatePicker extends PopupElement {
|
|||
this.monthsContainer.classList.add('date-picker-months');
|
||||
this.monthsContainer.addEventListener('click', this.onDateClick);
|
||||
|
||||
popupBody.append(this.controlsDiv, this.monthsContainer);
|
||||
this.container.append(popupBody);
|
||||
this.body.append(this.controlsDiv, this.monthsContainer);
|
||||
|
||||
// Time inputs
|
||||
if(options.withTime) {
|
||||
this.timeDiv = document.createElement('div');
|
||||
this.timeDiv.classList.add('date-picker-time');
|
||||
|
||||
const delimiter = document.createElement('div');
|
||||
delimiter.classList.add('date-picker-time-delimiter');
|
||||
delimiter.append(':');
|
||||
|
||||
const handleTimeInput = (max: number, inputField: InputField, onInput: (length: number) => void, onOverflow?: (number: number) => void) => {
|
||||
const maxString = '' + max;
|
||||
inputField.input.addEventListener('input', (e) => {
|
||||
let value = inputField.value.replace(/\D/g, '');
|
||||
if(value.length > 2) {
|
||||
value = value.slice(0, 2);
|
||||
} else {
|
||||
if((value.length === 1 && +value[0] > +maxString[0]) || (value.length === 2 && +value > max)) {
|
||||
if(value.length === 2 && onOverflow) {
|
||||
onOverflow(+value[1]);
|
||||
}
|
||||
|
||||
value = '0' + value[0];
|
||||
}
|
||||
}
|
||||
|
||||
inputField.setValueSilently(value);
|
||||
onInput(value.length);
|
||||
});
|
||||
};
|
||||
|
||||
this.hoursInputField = new InputField({plainText: true});
|
||||
this.minutesInputField = new InputField({plainText: true});
|
||||
|
||||
handleTimeInput(23, this.hoursInputField, (length) => {
|
||||
if(length === 2) {
|
||||
this.minutesInputField.input.focus();
|
||||
}
|
||||
|
||||
this.setTimeTitle();
|
||||
}, (number) => {
|
||||
this.minutesInputField.value = (number + this.minutesInputField.value).slice(0, 2);
|
||||
});
|
||||
handleTimeInput(59, this.minutesInputField, (length) => {
|
||||
if(!length) {
|
||||
this.hoursInputField.input.focus();
|
||||
}
|
||||
|
||||
this.setTimeTitle();
|
||||
});
|
||||
|
||||
this.selectedDate = initDate;
|
||||
|
||||
initDate.setMinutes(initDate.getMinutes() + 10);
|
||||
|
||||
this.hoursInputField.setValueSilently(('0' + initDate.getHours()).slice(-2));
|
||||
this.minutesInputField.setValueSilently(('0' + initDate.getMinutes()).slice(-2));
|
||||
|
||||
initDate.setHours(0, 0, 0, 0);
|
||||
|
||||
this.timeDiv.append(this.hoursInputField.container, delimiter, this.minutesInputField.container);
|
||||
|
||||
this.btnConfirm.addEventListener('click', () => {
|
||||
if(this.onPick) {
|
||||
this.selectedDate.setHours(+this.hoursInputField.value || 0, +this.minutesInputField.value || 0, 0, 0);
|
||||
this.onPick(this.selectedDate.getTime() / 1000 | 0);
|
||||
}
|
||||
|
||||
this.destroy();
|
||||
}, {once: true});
|
||||
|
||||
this.body.append(this.timeDiv);
|
||||
}
|
||||
|
||||
const popupCenterer = document.createElement('div');
|
||||
popupCenterer.classList.add('popup-centerer');
|
||||
|
@ -68,7 +151,7 @@ export default class PopupDatePicker extends PopupElement {
|
|||
initDate.setHours(0, 0, 0, 0);
|
||||
this.selectedDate = initDate;
|
||||
|
||||
this.maxDate = new Date();
|
||||
this.maxDate = options.maxDate || new Date();
|
||||
this.maxDate.setHours(0, 0, 0, 0);
|
||||
|
||||
this.selectedMonth = new Date(this.selectedDate);
|
||||
|
@ -78,6 +161,7 @@ export default class PopupDatePicker extends PopupElement {
|
|||
this.maxMonth.setDate(1);
|
||||
|
||||
this.minMonth = new Date(this.minDate);
|
||||
this.minMonth.setHours(0, 0, 0, 0);
|
||||
this.minMonth.setDate(1);
|
||||
|
||||
if(this.selectedMonth.getTime() == this.minMonth.getTime()) {
|
||||
|
@ -88,6 +172,11 @@ export default class PopupDatePicker extends PopupElement {
|
|||
this.nextBtn.setAttribute('disabled', 'true');
|
||||
}
|
||||
|
||||
if(options.noTitle) {
|
||||
this.setTitle = () => {};
|
||||
}
|
||||
|
||||
this.setTimeTitle();
|
||||
this.setTitle();
|
||||
this.setMonth();
|
||||
}
|
||||
|
@ -132,15 +221,37 @@ export default class PopupDatePicker extends PopupElement {
|
|||
|
||||
this.setTitle();
|
||||
this.setMonth();
|
||||
this.setTimeTitle();
|
||||
};
|
||||
|
||||
public setTimeTitle() {
|
||||
if(this.btnConfirm && this.selectedDate) {
|
||||
let dayStr = '';
|
||||
const date = new Date();
|
||||
date.setHours(0, 0, 0, 0);
|
||||
|
||||
if(this.selectedDate.getTime() === date.getTime()) {
|
||||
dayStr = 'Today';
|
||||
} else if(this.selectedDate.getTime() === (date.getTime() + 86400e3)) {
|
||||
dayStr = 'Tomorrow';
|
||||
} else {
|
||||
dayStr = 'on ' + getFullDate(this.selectedDate, {
|
||||
noTime: true,
|
||||
monthAsNumber: true,
|
||||
leadingZero: true
|
||||
});
|
||||
}
|
||||
|
||||
this.btnConfirm.innerText = 'Send ' + dayStr + ' at ' + ('00' + this.hoursInputField.value).slice(-2) + ':' + ('00' + this.minutesInputField.value).slice(-2);
|
||||
}
|
||||
}
|
||||
|
||||
public setTitle() {
|
||||
const splitted = this.selectedDate.toString().split(' ', 3);
|
||||
this.title.innerText = splitted[0] + ', ' + splitted[1] + ' ' + splitted[2];
|
||||
}
|
||||
|
||||
public setMonth() {
|
||||
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
||||
this.monthTitle.innerText = months[this.selectedMonth.getMonth()] + ' ' + this.selectedMonth.getFullYear();
|
||||
|
||||
if(this.month) {
|
||||
|
@ -176,11 +287,11 @@ export default class PopupDatePicker extends PopupElement {
|
|||
el.innerText = '' + date;
|
||||
el.dataset.timestamp = '' + firstDate.getTime();
|
||||
|
||||
if(firstDate > this.maxDate) {
|
||||
if(firstDate > this.maxDate || firstDate < this.minDate) {
|
||||
el.setAttribute('disabled', 'true');
|
||||
}
|
||||
|
||||
if(firstDate.getTime() == this.selectedDate.getTime()) {
|
||||
if(firstDate.getTime() === this.selectedDate.getTime()) {
|
||||
this.selectedEl = el;
|
||||
el.classList.add('active');
|
||||
}
|
||||
|
@ -188,7 +299,7 @@ export default class PopupDatePicker extends PopupElement {
|
|||
this.month.append(el);
|
||||
|
||||
firstDate.setDate(date + 1);
|
||||
} while(firstDate.getDate() != 1);
|
||||
} while(firstDate.getDate() !== 1);
|
||||
|
||||
this.container.classList.toggle('is-max-lines', (this.month.childElementCount / 7) > 6);
|
||||
|
|
@ -1,25 +1,30 @@
|
|||
import appChatsManager from "../lib/appManagers/appChatsManager";
|
||||
import appMessagesManager from "../lib/appManagers/appMessagesManager";
|
||||
import appPeersManager from "../lib/appManagers/appPeersManager";
|
||||
import rootScope from "../lib/rootScope";
|
||||
import { PopupButton } from "./popup";
|
||||
import PopupPeer from "./popupPeer";
|
||||
import appChatsManager from "../../lib/appManagers/appChatsManager";
|
||||
import appMessagesManager from "../../lib/appManagers/appMessagesManager";
|
||||
import appPeersManager from "../../lib/appManagers/appPeersManager";
|
||||
import rootScope from "../../lib/rootScope";
|
||||
import { PopupButton } from ".";
|
||||
import PopupPeer from "./peer";
|
||||
import { ChatType } from "../chat/chat";
|
||||
|
||||
export default class PopupDeleteMessages {
|
||||
constructor(peerId: number, mids: number[], onConfirm?: () => void) {
|
||||
constructor(peerId: number, mids: number[], type: ChatType, onConfirm?: () => void) {
|
||||
const firstName = appPeersManager.getPeerTitle(peerId, false, true);
|
||||
|
||||
mids = mids.slice();
|
||||
const callback = (revoke: boolean) => {
|
||||
onConfirm && onConfirm();
|
||||
appMessagesManager.deleteMessages(peerId, mids, revoke);
|
||||
if(type === 'scheduled') {
|
||||
appMessagesManager.deleteScheduledMessages(peerId, mids);
|
||||
} else {
|
||||
appMessagesManager.deleteMessages(peerId, mids, revoke);
|
||||
}
|
||||
};
|
||||
|
||||
let title: string, description: string, buttons: PopupButton[];
|
||||
title = `Delete ${mids.length == 1 ? '' : mids.length + ' '}Message${mids.length == 1 ? '' : 's'}?`;
|
||||
description = `Are you sure you want to delete ${mids.length == 1 ? 'this message' : 'these messages'}?`;
|
||||
|
||||
if(peerId == rootScope.myId) {
|
||||
if(peerId == rootScope.myId || type === 'scheduled') {
|
||||
buttons = [{
|
||||
text: 'DELETE',
|
||||
isDanger: true,
|
|
@ -1,7 +1,7 @@
|
|||
import { isTouchSupported } from "../helpers/touchSupport";
|
||||
import appImManager from "../lib/appManagers/appImManager";
|
||||
import AppSelectPeers from "./appSelectPeers";
|
||||
import { PopupElement } from "./popup";
|
||||
import { isTouchSupported } from "../../helpers/touchSupport";
|
||||
import appImManager from "../../lib/appManagers/appImManager";
|
||||
import AppSelectPeers from "../appSelectPeers";
|
||||
import PopupElement from ".";
|
||||
|
||||
export default class PopupForward extends PopupElement {
|
||||
private selector: AppSelectPeers;
|
||||
|
@ -14,7 +14,7 @@ export default class PopupForward extends PopupElement {
|
|||
|
||||
this.selector = new AppSelectPeers(this.body, async() => {
|
||||
const peerId = this.selector.getSelected()[0];
|
||||
this.closeBtn.click();
|
||||
this.btnClose.click();
|
||||
|
||||
this.selector = null;
|
||||
|
|
@ -1,21 +1,22 @@
|
|||
import rootScope from "../lib/rootScope";
|
||||
import { blurActiveElement, cancelEvent, findUpClassName } from "../helpers/dom";
|
||||
import { ripple } from "./ripple";
|
||||
import rootScope from "../../lib/rootScope";
|
||||
import { blurActiveElement, cancelEvent, findUpClassName } from "../../helpers/dom";
|
||||
import { ripple } from "../ripple";
|
||||
|
||||
export class PopupElement {
|
||||
export type PopupOptions = Partial<{closable: true, overlayClosable: true, withConfirm: string, body: true}>;
|
||||
export default class PopupElement {
|
||||
protected element = document.createElement('div');
|
||||
protected container = document.createElement('div');
|
||||
protected header = document.createElement('div');
|
||||
protected title = document.createElement('div');
|
||||
protected closeBtn: HTMLElement;
|
||||
protected confirmBtn: HTMLElement;
|
||||
protected btnClose: HTMLElement;
|
||||
protected btnConfirm: HTMLElement;
|
||||
protected body: HTMLElement;
|
||||
|
||||
protected onClose: () => void;
|
||||
protected onCloseAfterTimeout: () => void;
|
||||
protected onEscape: () => boolean = () => true;
|
||||
|
||||
constructor(className: string, buttons?: Array<PopupButton>, options: Partial<{closable: true, overlayClosable: true, withConfirm: string, body: true}> = {}) {
|
||||
constructor(className: string, buttons?: Array<PopupButton>, options: PopupOptions = {}) {
|
||||
this.element.classList.add('popup');
|
||||
this.element.className = 'popup' + (className ? ' ' + className : '');
|
||||
this.container.classList.add('popup-container', 'z-depth-1');
|
||||
|
@ -26,17 +27,17 @@ export class PopupElement {
|
|||
this.header.append(this.title);
|
||||
|
||||
if(options.closable) {
|
||||
this.closeBtn = document.createElement('span');
|
||||
this.closeBtn.classList.add('btn-icon', 'popup-close', 'tgico-close');
|
||||
this.btnClose = document.createElement('span');
|
||||
this.btnClose.classList.add('btn-icon', 'popup-close', 'tgico-close');
|
||||
//ripple(this.closeBtn);
|
||||
this.header.prepend(this.closeBtn);
|
||||
this.header.prepend(this.btnClose);
|
||||
|
||||
this.closeBtn.addEventListener('click', this.destroy, {once: true});
|
||||
this.btnClose.addEventListener('click', this.destroy, {once: true});
|
||||
|
||||
if(options.overlayClosable) {
|
||||
const onOverlayClick = (e: MouseEvent) => {
|
||||
if(!findUpClassName(e.target, 'popup-container')) {
|
||||
this.closeBtn.click();
|
||||
this.btnClose.click();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -47,11 +48,11 @@ export class PopupElement {
|
|||
window.addEventListener('keydown', this._onKeyDown, {capture: true});
|
||||
|
||||
if(options.withConfirm) {
|
||||
this.confirmBtn = document.createElement('button');
|
||||
this.confirmBtn.classList.add('btn-primary');
|
||||
this.confirmBtn.innerText = options.withConfirm;
|
||||
this.header.append(this.confirmBtn);
|
||||
ripple(this.confirmBtn);
|
||||
this.btnConfirm = document.createElement('button');
|
||||
this.btnConfirm.classList.add('btn-primary');
|
||||
this.btnConfirm.innerText = options.withConfirm;
|
||||
this.header.append(this.btnConfirm);
|
||||
ripple(this.btnConfirm);
|
||||
}
|
||||
|
||||
this.container.append(this.header);
|
||||
|
@ -112,7 +113,7 @@ export class PopupElement {
|
|||
this.element.classList.remove('active');
|
||||
|
||||
window.removeEventListener('keydown', this._onKeyDown, {capture: true});
|
||||
if(this.closeBtn) this.closeBtn.removeEventListener('click', this.destroy);
|
||||
if(this.btnClose) this.btnClose.removeEventListener('click', this.destroy);
|
||||
rootScope.overlayIsActive = false;
|
||||
|
||||
setTimeout(() => {
|
|
@ -1,14 +1,13 @@
|
|||
import { isTouchSupported } from "../helpers/touchSupport";
|
||||
import appImManager from "../lib/appManagers/appImManager";
|
||||
import appMessagesManager from "../lib/appManagers/appMessagesManager";
|
||||
import { calcImageInBox, getRichValue } from "../helpers/dom";
|
||||
import InputField from "./inputField";
|
||||
import { PopupElement } from "./popup";
|
||||
import { ripple } from "./ripple";
|
||||
import Scrollable from "./scrollable";
|
||||
import { toast } from "./toast";
|
||||
import { prepareAlbum, wrapDocument } from "./wrappers";
|
||||
import CheckboxField from "./checkbox";
|
||||
import type Chat from "../chat/chat";
|
||||
import { isTouchSupported } from "../../helpers/touchSupport";
|
||||
import { calcImageInBox, placeCaretAtEnd } from "../../helpers/dom";
|
||||
import InputField from "../inputField";
|
||||
import PopupElement from ".";
|
||||
import Scrollable from "../scrollable";
|
||||
import { toast } from "../toast";
|
||||
import { prepareAlbum, wrapDocument } from "../wrappers";
|
||||
import CheckboxField from "../checkbox";
|
||||
import SendContextMenu from "../chat/sendContextMenu";
|
||||
|
||||
type SendFileParams = Partial<{
|
||||
file: File,
|
||||
|
@ -23,10 +22,10 @@ const MAX_LENGTH_CAPTION = 1024;
|
|||
// TODO: .gif upload as video
|
||||
|
||||
export default class PopupNewMedia extends PopupElement {
|
||||
private btnSend: HTMLElement;
|
||||
private input: HTMLInputElement;
|
||||
private input: HTMLElement;
|
||||
private mediaContainer: HTMLElement;
|
||||
private groupCheckboxField: { label: HTMLLabelElement; input: HTMLInputElement; span: HTMLSpanElement; };
|
||||
private wasInputValue = '';
|
||||
|
||||
private willAttach: Partial<{
|
||||
type: 'media' | 'document',
|
||||
|
@ -37,39 +36,57 @@ export default class PopupNewMedia extends PopupElement {
|
|||
sendFileDetails: [],
|
||||
group: false
|
||||
};
|
||||
inputField: InputField;
|
||||
|
||||
constructor(files: File[], willAttachType: PopupNewMedia['willAttach']['type']) {
|
||||
super('popup-send-photo popup-new-media', null, {closable: true});
|
||||
constructor(private chat: Chat, files: File[], willAttachType: PopupNewMedia['willAttach']['type']) {
|
||||
super('popup-send-photo popup-new-media', null, {closable: true, withConfirm: 'SEND'});
|
||||
|
||||
this.willAttach.type = willAttachType;
|
||||
|
||||
this.btnSend = document.createElement('button');
|
||||
this.btnSend.className = 'btn-primary';
|
||||
this.btnSend.innerText = 'SEND';
|
||||
ripple(this.btnSend);
|
||||
this.btnSend.addEventListener('click', this.send);
|
||||
|
||||
this.header.append(this.btnSend);
|
||||
this.btnConfirm.addEventListener('click', () => this.send());
|
||||
|
||||
if(this.chat.type !== 'scheduled') {
|
||||
const sendMenu = new SendContextMenu({
|
||||
onSilentClick: () => {
|
||||
this.chat.input.sendSilent = true;
|
||||
this.send();
|
||||
},
|
||||
onScheduleClick: () => {
|
||||
this.chat.input.scheduleSending(() => {
|
||||
this.send();
|
||||
});
|
||||
},
|
||||
openSide: 'bottom-left',
|
||||
onContextElement: this.btnConfirm,
|
||||
});
|
||||
|
||||
sendMenu.setPeerId(this.chat.peerId);
|
||||
|
||||
this.header.append(sendMenu.sendMenu);
|
||||
}
|
||||
|
||||
this.mediaContainer = document.createElement('div');
|
||||
this.mediaContainer.classList.add('popup-photo');
|
||||
const scrollable = new Scrollable(null);
|
||||
scrollable.container.append(this.mediaContainer);
|
||||
|
||||
const inputField = InputField({
|
||||
this.inputField = new InputField({
|
||||
placeholder: 'Add a caption...',
|
||||
label: 'Caption',
|
||||
name: 'photo-caption',
|
||||
maxLength: MAX_LENGTH_CAPTION,
|
||||
showLengthOn: 80
|
||||
});
|
||||
this.input = inputField.input;
|
||||
this.input = this.inputField.input;
|
||||
|
||||
this.inputField.value = this.wasInputValue = this.chat.input.messageInputField.value;
|
||||
this.chat.input.messageInputField.value = '';
|
||||
|
||||
this.container.append(scrollable.container);
|
||||
|
||||
if(files.length > 1) {
|
||||
this.groupCheckboxField = CheckboxField('Group items', 'group-items');
|
||||
this.container.append(this.groupCheckboxField.label, inputField.container);
|
||||
this.container.append(this.groupCheckboxField.label, this.inputField.container);
|
||||
|
||||
this.groupCheckboxField.input.checked = true;
|
||||
this.willAttach.group = true;
|
||||
|
@ -86,7 +103,7 @@ export default class PopupNewMedia extends PopupElement {
|
|||
});
|
||||
}
|
||||
|
||||
this.container.append(inputField.container);
|
||||
this.container.append(this.inputField.container);
|
||||
|
||||
this.attachFiles(files);
|
||||
}
|
||||
|
@ -95,15 +112,24 @@ export default class PopupNewMedia extends PopupElement {
|
|||
const target = e.target as HTMLElement;
|
||||
if(target.tagName != 'INPUT') {
|
||||
this.input.focus();
|
||||
placeCaretAtEnd(this.input);
|
||||
}
|
||||
|
||||
if(e.key == 'Enter' && !isTouchSupported) {
|
||||
this.btnSend.click();
|
||||
this.btnConfirm.click();
|
||||
}
|
||||
};
|
||||
|
||||
public send = () => {
|
||||
let caption = getRichValue(this.input);
|
||||
public send(force = false) {
|
||||
if(this.chat.type === 'scheduled' && !force) {
|
||||
this.chat.input.scheduleSending(() => {
|
||||
this.send(true);
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let caption = this.inputField.value;
|
||||
if(caption.length > MAX_LENGTH_CAPTION) {
|
||||
toast('Caption is too long.');
|
||||
return;
|
||||
|
@ -115,8 +141,10 @@ export default class PopupNewMedia extends PopupElement {
|
|||
|
||||
//console.log('will send files with options:', willAttach);
|
||||
|
||||
const peerId = appImManager.chat.peerId;
|
||||
const chatInputC = appImManager.chat.input;
|
||||
const peerId = this.chat.peerId;
|
||||
const input = this.chat.input;
|
||||
const silent = input.sendSilent;
|
||||
const scheduleDate = input.scheduleDate;
|
||||
|
||||
if(willAttach.sendFileDetails.length > 1 && willAttach.group) {
|
||||
for(let i = 0; i < willAttach.sendFileDetails.length;) {
|
||||
|
@ -131,34 +159,38 @@ export default class PopupNewMedia extends PopupElement {
|
|||
const w = {...willAttach};
|
||||
w.sendFileDetails = willAttach.sendFileDetails.slice(i - k, i);
|
||||
|
||||
appMessagesManager.sendAlbum(peerId, w.sendFileDetails.map(d => d.file), Object.assign({
|
||||
this.chat.appMessagesManager.sendAlbum(peerId, w.sendFileDetails.map(d => d.file), Object.assign({
|
||||
caption,
|
||||
replyToMsgId: chatInputC.replyToMsgId,
|
||||
isMedia: willAttach.isMedia
|
||||
replyToMsgId: input.replyToMsgId,
|
||||
isMedia: willAttach.isMedia,
|
||||
silent,
|
||||
scheduleDate
|
||||
}, w));
|
||||
|
||||
caption = undefined;
|
||||
chatInputC.replyToMsgId = 0;
|
||||
input.replyToMsgId = undefined;
|
||||
}
|
||||
} else {
|
||||
if(caption) {
|
||||
if(willAttach.sendFileDetails.length > 1) {
|
||||
appMessagesManager.sendText(peerId, caption, {replyToMsgId: chatInputC.replyToMsgId});
|
||||
this.chat.appMessagesManager.sendText(peerId, caption, {replyToMsgId: input.replyToMsgId, silent, scheduleDate});
|
||||
caption = '';
|
||||
chatInputC.replyToMsgId = 0;
|
||||
input.replyToMsgId = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const promises = willAttach.sendFileDetails.map(params => {
|
||||
const promise = appMessagesManager.sendFile(peerId, params.file, Object.assign({
|
||||
const promise = this.chat.appMessagesManager.sendFile(peerId, params.file, Object.assign({
|
||||
//isMedia: willAttach.isMedia,
|
||||
isMedia: willAttach.isMedia,
|
||||
caption,
|
||||
replyToMsgId: chatInputC.replyToMsgId
|
||||
replyToMsgId: input.replyToMsgId,
|
||||
silent,
|
||||
scheduleDate
|
||||
}, params));
|
||||
|
||||
caption = '';
|
||||
chatInputC.replyToMsgId = 0;
|
||||
input.replyToMsgId = undefined;
|
||||
return promise;
|
||||
});
|
||||
}
|
||||
|
@ -167,8 +199,8 @@ export default class PopupNewMedia extends PopupElement {
|
|||
|
||||
//appMessagesManager.sendFile(appImManager.peerId, willAttach.file, willAttach);
|
||||
|
||||
chatInputC.onMessageSent();
|
||||
};
|
||||
input.onMessageSent();
|
||||
}
|
||||
|
||||
public attachFile = (file: File) => {
|
||||
const willAttach = this.willAttach;
|
||||
|
@ -343,6 +375,10 @@ export default class PopupNewMedia extends PopupElement {
|
|||
if(!this.element.classList.contains('active')) {
|
||||
document.body.addEventListener('keydown', this.onKeyDown);
|
||||
this.onClose = () => {
|
||||
if(this.wasInputValue) {
|
||||
this.chat.input.messageInputField.value = this.wasInputValue;
|
||||
}
|
||||
|
||||
document.body.removeEventListener('keydown', this.onKeyDown);
|
||||
};
|
||||
this.show();
|
|
@ -1,5 +1,5 @@
|
|||
import AvatarElement from "./avatar";
|
||||
import { PopupElement, PopupButton } from "./popup";
|
||||
import AvatarElement from "../avatar";
|
||||
import PopupElement, { PopupButton } from ".";
|
||||
|
||||
export default class PopupPeer extends PopupElement {
|
||||
constructor(private className: string, options: Partial<{
|
32
src/components/popups/schedule.ts
Normal file
32
src/components/popups/schedule.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
import PopupDatePicker from "./datePicker";
|
||||
|
||||
const getMinDate = () => {
|
||||
const date = new Date();
|
||||
date.setDate(date.getDate() - 1);
|
||||
//date.setHours(0, 0, 0, 0);
|
||||
return date;
|
||||
};
|
||||
|
||||
export default class PopupSchedule extends PopupDatePicker {
|
||||
constructor(initDate: Date, onPick: (timestamp: number) => void) {
|
||||
super(initDate, onPick, {
|
||||
noButtons: true,
|
||||
noTitle: true,
|
||||
closable: true,
|
||||
withConfirm: 'Send Today',
|
||||
minDate: getMinDate(),
|
||||
maxDate: (() => {
|
||||
const date = new Date();
|
||||
date.setFullYear(date.getFullYear() + 1);
|
||||
date.setDate(date.getDate() - 1);
|
||||
return date;
|
||||
})(),
|
||||
withTime: true
|
||||
});
|
||||
|
||||
this.element.classList.add('popup-schedule');
|
||||
this.header.append(this.controlsDiv);
|
||||
this.title.replaceWith(this.monthTitle);
|
||||
this.body.append(this.btnConfirm);
|
||||
}
|
||||
}
|
36
src/components/popups/sendNow.ts
Normal file
36
src/components/popups/sendNow.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
import appMessagesManager from "../../lib/appManagers/appMessagesManager";
|
||||
import { PopupButton } from ".";
|
||||
import PopupPeer from "./peer";
|
||||
|
||||
export default class PopupSendNow {
|
||||
constructor(peerId: number, mids: number[], onConfirm?: () => void) {
|
||||
let title: string, description: string, buttons: PopupButton[] = [];
|
||||
|
||||
title = `Send Message${mids.length > 1 ? 's' : ''} Now`;
|
||||
description = mids.length > 1 ? 'Send ' + mids.length + ' messages now?' : 'Send message now?';
|
||||
|
||||
const callback = () => {
|
||||
onConfirm && onConfirm();
|
||||
appMessagesManager.sendScheduledMessages(peerId, mids);
|
||||
};
|
||||
|
||||
buttons.push({
|
||||
text: 'SEND',
|
||||
callback
|
||||
});
|
||||
|
||||
buttons.push({
|
||||
text: 'CANCEL',
|
||||
isCancel: true
|
||||
});
|
||||
|
||||
const popup = new PopupPeer('popup-delete-chat', {
|
||||
peerId,
|
||||
title,
|
||||
description,
|
||||
buttons
|
||||
});
|
||||
|
||||
popup.show();
|
||||
}
|
||||
}
|
|
@ -1,15 +1,15 @@
|
|||
import { PopupElement } from "./popup";
|
||||
import appStickersManager from "../lib/appManagers/appStickersManager";
|
||||
import { RichTextProcessor } from "../lib/richtextprocessor";
|
||||
import Scrollable from "./scrollable";
|
||||
import { wrapSticker } from "./wrappers";
|
||||
import LazyLoadQueue from "./lazyLoadQueue";
|
||||
import { putPreloader } from "./misc";
|
||||
import animationIntersector from "./animationIntersector";
|
||||
import { findUpClassName } from "../helpers/dom";
|
||||
import appImManager from "../lib/appManagers/appImManager";
|
||||
import { StickerSet } from "../layer";
|
||||
import mediaSizes from "../helpers/mediaSizes";
|
||||
import PopupElement from ".";
|
||||
import appStickersManager from "../../lib/appManagers/appStickersManager";
|
||||
import { RichTextProcessor } from "../../lib/richtextprocessor";
|
||||
import Scrollable from "../scrollable";
|
||||
import { wrapSticker } from "../wrappers";
|
||||
import LazyLoadQueue from "../lazyLoadQueue";
|
||||
import { putPreloader } from "../misc";
|
||||
import animationIntersector from "../animationIntersector";
|
||||
import { findUpClassName } from "../../helpers/dom";
|
||||
import appImManager from "../../lib/appManagers/appImManager";
|
||||
import { StickerSet } from "../../layer";
|
||||
import mediaSizes from "../../helpers/mediaSizes";
|
||||
|
||||
const ANIMATION_GROUP = 'STICKERS-POPUP';
|
||||
|
||||
|
@ -74,7 +74,7 @@ export default class PopupStickers extends PopupElement {
|
|||
this.stickersFooter.setAttribute('disabled', 'true');
|
||||
|
||||
appStickersManager.toggleStickerSet(this.set).then(() => {
|
||||
this.closeBtn.click();
|
||||
this.btnClose.click();
|
||||
}).catch(() => {
|
||||
this.stickersFooter.removeAttribute('disabled');
|
||||
});
|
||||
|
@ -86,7 +86,7 @@ export default class PopupStickers extends PopupElement {
|
|||
|
||||
const fileId = target.dataset.docId;
|
||||
if(appImManager.chat.input.sendMessageWithDocument(fileId)) {
|
||||
this.closeBtn.click();
|
||||
this.btnClose.click();
|
||||
} else {
|
||||
console.warn('got no doc by id:', fileId);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import appMessagesManager from "../lib/appManagers/appMessagesManager";
|
||||
import { PopupButton } from "./popup";
|
||||
import PopupPeer from "./popupPeer";
|
||||
import appMessagesManager from "../../lib/appManagers/appMessagesManager";
|
||||
import { PopupButton } from ".";
|
||||
import PopupPeer from "./peer";
|
||||
|
||||
export default class PopupPinMessage {
|
||||
constructor(peerId: number, mid: number, unpin?: true) {
|
|
@ -1,5 +1,4 @@
|
|||
import appSidebarLeft from "..";
|
||||
import { getRichValue } from "../../../helpers/dom";
|
||||
import { InputFile } from "../../../layer";
|
||||
import appProfileManager from "../../../lib/appManagers/appProfileManager";
|
||||
import appUsersManager from "../../../lib/appManagers/appUsersManager";
|
||||
|
@ -8,7 +7,7 @@ import RichTextProcessor from "../../../lib/richtextprocessor";
|
|||
import rootScope from "../../../lib/rootScope";
|
||||
import AvatarElement from "../../avatar";
|
||||
import InputField from "../../inputField";
|
||||
import PopupAvatar from "../../popupAvatar";
|
||||
import PopupAvatar from "../../popups/avatar";
|
||||
import Scrollable from "../../scrollable";
|
||||
import { SliderTab } from "../../slider";
|
||||
|
||||
|
@ -21,10 +20,10 @@ export default class AppEditProfileTab implements SliderTab {
|
|||
private canvas: HTMLCanvasElement;
|
||||
private uploadAvatar: () => Promise<InputFile> = null;
|
||||
|
||||
private firstNameInput: HTMLInputElement;
|
||||
private lastNameInput: HTMLInputElement;
|
||||
private bioInput: HTMLInputElement;
|
||||
private userNameInput: HTMLInputElement;
|
||||
private firstNameInput: HTMLElement;
|
||||
private lastNameInput: HTMLElement;
|
||||
private bioInput: HTMLElement;
|
||||
private userNameInput: HTMLElement;
|
||||
|
||||
private avatarElem: AvatarElement;
|
||||
|
||||
|
@ -37,6 +36,10 @@ export default class AppEditProfileTab implements SliderTab {
|
|||
userName: '',
|
||||
bio: ''
|
||||
};
|
||||
firstNameInputField: InputField;
|
||||
lastNameInputField: InputField;
|
||||
bioInputField: InputField;
|
||||
userNameInputField: InputField;
|
||||
|
||||
public init() {
|
||||
this.container = document.querySelector('.edit-profile-container');
|
||||
|
@ -63,27 +66,27 @@ export default class AppEditProfileTab implements SliderTab {
|
|||
const inputWrapper = document.createElement('div');
|
||||
inputWrapper.classList.add('input-wrapper');
|
||||
|
||||
const firstNameInputField = InputField({
|
||||
this.firstNameInputField = new InputField({
|
||||
label: 'Name',
|
||||
name: 'first-name',
|
||||
maxLength: 70
|
||||
});
|
||||
const lastNameInputField = InputField({
|
||||
this.lastNameInputField = new InputField({
|
||||
label: 'Last Name',
|
||||
name: 'last-name',
|
||||
maxLength: 64
|
||||
});
|
||||
const bioInputField = InputField({
|
||||
this.bioInputField = new InputField({
|
||||
label: 'Bio (optional)',
|
||||
name: 'bio',
|
||||
maxLength: 70
|
||||
});
|
||||
|
||||
this.firstNameInput = firstNameInputField.input;
|
||||
this.lastNameInput = lastNameInputField.input;
|
||||
this.bioInput = bioInputField.input;
|
||||
this.firstNameInput = this.firstNameInputField.input;
|
||||
this.lastNameInput = this.lastNameInputField.input;
|
||||
this.bioInput = this.bioInputField.input;
|
||||
|
||||
inputWrapper.append(firstNameInputField.container, lastNameInputField.container, bioInputField.container);
|
||||
inputWrapper.append(this.firstNameInputField.container, this.lastNameInputField.container, this.bioInputField.container);
|
||||
avatarEdit.parentElement.insertBefore(inputWrapper, avatarEdit.nextElementSibling);
|
||||
}
|
||||
|
||||
|
@ -91,14 +94,14 @@ export default class AppEditProfileTab implements SliderTab {
|
|||
const inputWrapper = document.createElement('div');
|
||||
inputWrapper.classList.add('input-wrapper');
|
||||
|
||||
const userNameInputField = InputField({
|
||||
this.userNameInputField = new InputField({
|
||||
label: 'Username (optional)',
|
||||
name: 'username',
|
||||
plainText: true
|
||||
});
|
||||
this.userNameInput = userNameInputField.input;
|
||||
this.userNameInput = this.userNameInputField.input;
|
||||
|
||||
inputWrapper.append(userNameInputField.container);
|
||||
inputWrapper.append(this.userNameInputField.container);
|
||||
|
||||
const caption = this.profileUrlContainer.parentElement;
|
||||
caption.parentElement.insertBefore(inputWrapper, caption);
|
||||
|
@ -110,7 +113,7 @@ export default class AppEditProfileTab implements SliderTab {
|
|||
this.lastNameInput.addEventListener('input', this.handleChange);
|
||||
this.bioInput.addEventListener('input', this.handleChange);
|
||||
this.userNameInput.addEventListener('input', () => {
|
||||
let value = this.userNameInput.value;
|
||||
let value = this.userNameInputField.value;
|
||||
|
||||
//console.log('userNameInput:', value);
|
||||
if(value == this.originalValues.userName || !value.length) {
|
||||
|
@ -136,7 +139,7 @@ export default class AppEditProfileTab implements SliderTab {
|
|||
apiManager.invokeApi('account.checkUsername', {
|
||||
username: value
|
||||
}).then(available => {
|
||||
if(this.userNameInput.value != value) return;
|
||||
if(this.userNameInputField.value != value) return;
|
||||
|
||||
if(available) {
|
||||
this.userNameInput.classList.add('valid');
|
||||
|
@ -148,7 +151,7 @@ export default class AppEditProfileTab implements SliderTab {
|
|||
userNameLabel.innerText = 'Username is already taken';
|
||||
}
|
||||
}, (err) => {
|
||||
if(this.userNameInput.value != value) return;
|
||||
if(this.userNameInputField.value != value) return;
|
||||
|
||||
switch(err.type) {
|
||||
case 'USERNAME_INVALID': {
|
||||
|
@ -169,7 +172,7 @@ export default class AppEditProfileTab implements SliderTab {
|
|||
|
||||
let promises: Promise<any>[] = [];
|
||||
|
||||
promises.push(appProfileManager.updateProfile(getRichValue(this.firstNameInput), getRichValue(this.lastNameInput), getRichValue(this.bioInput)).then(() => {
|
||||
promises.push(appProfileManager.updateProfile(this.firstNameInputField.value, this.lastNameInputField.value, this.bioInputField.value).then(() => {
|
||||
appSidebarLeft.selectTab(0);
|
||||
}, (err) => {
|
||||
console.error('updateProfile error:', err);
|
||||
|
@ -181,8 +184,8 @@ export default class AppEditProfileTab implements SliderTab {
|
|||
}));
|
||||
}
|
||||
|
||||
if(this.userNameInput.value != this.originalValues.userName && this.userNameInput.classList.contains('valid')) {
|
||||
promises.push(appProfileManager.updateUsername(this.userNameInput.value));
|
||||
if(this.userNameInputField.value != this.originalValues.userName && this.userNameInput.classList.contains('valid')) {
|
||||
promises.push(appProfileManager.updateUsername(this.userNameInputField.value));
|
||||
}
|
||||
|
||||
Promise.race(promises).finally(() => {
|
||||
|
@ -211,7 +214,7 @@ export default class AppEditProfileTab implements SliderTab {
|
|||
this.firstNameInput.innerHTML = user.rFirstName;
|
||||
this.lastNameInput.innerHTML = RichTextProcessor.wrapRichText(user.last_name, {noLinks: true, noLinebreaks: true});
|
||||
this.bioInput.innerHTML = '';
|
||||
this.userNameInput.value = this.originalValues.userName = user.username ?? '';
|
||||
this.userNameInputField.value = this.originalValues.userName = user.username ?? '';
|
||||
|
||||
this.userNameInput.classList.remove('valid', 'error');
|
||||
this.userNameInput.nextElementSibling.innerHTML = 'Username (optional)';
|
||||
|
@ -242,18 +245,18 @@ export default class AppEditProfileTab implements SliderTab {
|
|||
|
||||
private isChanged() {
|
||||
return !!this.uploadAvatar
|
||||
|| (!this.firstNameInput.classList.contains('error') && getRichValue(this.firstNameInput) != this.originalValues.firstName)
|
||||
|| (!this.lastNameInput.classList.contains('error') && getRichValue(this.lastNameInput) != this.originalValues.lastName)
|
||||
|| (!this.bioInput.classList.contains('error') && getRichValue(this.bioInput) != this.originalValues.bio)
|
||||
|| (this.userNameInput.value != this.originalValues.userName && !this.userNameInput.classList.contains('error'));
|
||||
|| (!this.firstNameInput.classList.contains('error') && this.firstNameInputField.value != this.originalValues.firstName)
|
||||
|| (!this.lastNameInput.classList.contains('error') && this.lastNameInputField.value != this.originalValues.lastName)
|
||||
|| (!this.bioInput.classList.contains('error') && this.bioInputField.value != this.originalValues.bio)
|
||||
|| (this.userNameInputField.value != this.originalValues.userName && !this.userNameInput.classList.contains('error'));
|
||||
}
|
||||
|
||||
private setProfileUrl() {
|
||||
if(this.userNameInput.classList.contains('error') || !this.userNameInput.value.length) {
|
||||
if(this.userNameInput.classList.contains('error') || !this.userNameInputField.value.length) {
|
||||
this.profileUrlContainer.style.display = 'none';
|
||||
} else {
|
||||
this.profileUrlContainer.style.display = '';
|
||||
let url = 'https://t.me/' + this.userNameInput.value;
|
||||
let url = 'https://t.me/' + this.userNameInputField.value;
|
||||
this.profileUrlAnchor.innerText = url;
|
||||
this.profileUrlAnchor.href = url;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import appSidebarLeft, { AppSidebarLeft } from "..";
|
||||
import { InputFile } from "../../../layer";
|
||||
import appChatsManager from "../../../lib/appManagers/appChatsManager";
|
||||
import PopupAvatar from "../../popupAvatar";
|
||||
import PopupAvatar from "../../popups/avatar";
|
||||
import { SliderTab } from "../../slider";
|
||||
|
||||
export default class AppNewChannelTab implements SliderTab {
|
||||
|
|
|
@ -4,7 +4,7 @@ import appChatsManager from "../../../lib/appManagers/appChatsManager";
|
|||
import appDialogsManager from "../../../lib/appManagers/appDialogsManager";
|
||||
import appUsersManager from "../../../lib/appManagers/appUsersManager";
|
||||
import { SearchGroup } from "../../appSearch";
|
||||
import PopupAvatar from "../../popupAvatar";
|
||||
import PopupAvatar from "../../popups/avatar";
|
||||
import Scrollable from "../../scrollable";
|
||||
import { SliderTab } from "../../slider";
|
||||
|
||||
|
|
|
@ -14,9 +14,7 @@ export default class AppPollResultsTab implements SliderTab {
|
|||
private resultsDiv = this.contentDiv.firstElementChild as HTMLDivElement;
|
||||
private scrollable: Scrollable;
|
||||
|
||||
private peerId: number;
|
||||
private pollId: string;
|
||||
private mid: number;
|
||||
private message: any;
|
||||
|
||||
constructor() {
|
||||
this.scrollable = new Scrollable(this.contentDiv, 'POLL-RESULTS');
|
||||
|
@ -24,26 +22,23 @@ export default class AppPollResultsTab implements SliderTab {
|
|||
|
||||
public cleanup() {
|
||||
this.resultsDiv.innerHTML = '';
|
||||
this.pollId = '';
|
||||
this.mid = 0;
|
||||
this.message = undefined;
|
||||
}
|
||||
|
||||
public onCloseAfterTimeout() {
|
||||
this.cleanup();
|
||||
}
|
||||
|
||||
public init(peerId: number, pollId: string, mid: number) {
|
||||
if(this.peerId == peerId && this.pollId == pollId && this.mid == mid) return;
|
||||
public init(message: any) {
|
||||
if(this.message === message) return;
|
||||
|
||||
this.cleanup();
|
||||
|
||||
this.peerId = peerId;
|
||||
this.pollId = pollId;
|
||||
this.mid = mid;
|
||||
this.message = message;
|
||||
|
||||
appSidebarRight.selectTab(AppSidebarRight.SLIDERITEMSIDS.pollResults);
|
||||
|
||||
const poll = appPollsManager.getPoll(pollId);
|
||||
const poll = appPollsManager.getPoll(message.media.poll.id);
|
||||
|
||||
const title = document.createElement('h3');
|
||||
title.innerHTML = poll.poll.rQuestion;
|
||||
|
@ -88,7 +83,7 @@ export default class AppPollResultsTab implements SliderTab {
|
|||
if(loading) return;
|
||||
loading = true;
|
||||
|
||||
appPollsManager.getVotes(peerId, mid, answer.option, offset, limit).then(votesList => {
|
||||
appPollsManager.getVotes(message, answer.option, offset, limit).then(votesList => {
|
||||
votesList.votes.forEach(vote => {
|
||||
const {dom} = appDialogsManager.addDialogNew({
|
||||
dialog: vote.user_id,
|
||||
|
|
|
@ -498,7 +498,7 @@ export default class AppSharedMediaTab implements SliderTab {
|
|||
div.append(img);
|
||||
|
||||
if(isDownloaded || willHaveThumb) {
|
||||
const promise = new Promise((resolve, reject) => {
|
||||
const promise = new Promise<void>((resolve, reject) => {
|
||||
(thumb || img).addEventListener('load', () => {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
|
@ -542,14 +542,14 @@ export default class AppSharedMediaTab implements SliderTab {
|
|||
if(message.media?.webpage && message.media.webpage._ != 'webPageEmpty') {
|
||||
webpage = message.media.webpage;
|
||||
} else {
|
||||
const entity = message.totalEntities.find((e: any) => e._ == 'messageEntityUrl' || e._ == 'messageEntityTextUrl');
|
||||
const entity = message.totalEntities ? message.totalEntities.find((e: any) => e._ == 'messageEntityUrl' || e._ == 'messageEntityTextUrl') : null;
|
||||
let url: string, display_url: string, sliced: string;
|
||||
|
||||
if(!entity) {
|
||||
this.log.error('NO ENTITY:', message);
|
||||
//this.log.error('NO ENTITY:', message);
|
||||
const match = RichTextProcessor.matchUrl(message.message);
|
||||
if(!match) {
|
||||
this.log.error('NO ENTITY AND NO MATCH:', message);
|
||||
//this.log.error('NO ENTITY AND NO MATCH:', message);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -745,7 +745,8 @@ export default class AppSharedMediaTab implements SliderTab {
|
|||
//let loadCount = history.length ? 50 : 15;
|
||||
return this.loadSidebarMediaPromises[type] = appMessagesManager.getSearch(peerId, '', {_: type}, maxId, loadCount)
|
||||
.then(value => {
|
||||
history.push(...value.history);
|
||||
const mids = value.history.map(message => message.mid);
|
||||
history.push(...mids);
|
||||
|
||||
this.log(logStr + 'search house of glass', type, value);
|
||||
|
||||
|
@ -783,7 +784,7 @@ export default class AppSharedMediaTab implements SliderTab {
|
|||
}
|
||||
|
||||
//if(value.history.length) {
|
||||
return this.performSearchResult(this.filterMessagesByType(value.history, type), type);
|
||||
return this.performSearchResult(this.filterMessagesByType(mids, type), type);
|
||||
//}
|
||||
}).catch(err => {
|
||||
this.log.error('load error:', err);
|
||||
|
|
|
@ -5,7 +5,7 @@ import LazyLoadQueue from "../../lazyLoadQueue";
|
|||
import { findUpClassName } from "../../../helpers/dom";
|
||||
import appImManager from "../../../lib/appManagers/appImManager";
|
||||
import appStickersManager from "../../../lib/appManagers/appStickersManager";
|
||||
import PopupStickers from "../../popupStickers";
|
||||
import PopupStickers from "../../popups/stickers";
|
||||
import animationIntersector from "../../animationIntersector";
|
||||
import { RichTextProcessor } from "../../../lib/richtextprocessor";
|
||||
import { wrapSticker } from "../../wrappers";
|
||||
|
|
|
@ -1041,10 +1041,11 @@ export function wrapGroupedDocuments({albumMustBeRenderedFull, message, bubble,
|
|||
return nameContainer;
|
||||
}
|
||||
|
||||
export function wrapPoll(peerId: number, pollId: string, mid: number) {
|
||||
export function wrapPoll(message: any) {
|
||||
const elem = new PollElement();
|
||||
elem.setAttribute('peer-id', '' + peerId);
|
||||
elem.setAttribute('poll-id', pollId);
|
||||
elem.setAttribute('message-id', '' + mid);
|
||||
elem.message = message;
|
||||
elem.setAttribute('peer-id', '' + message.peerId);
|
||||
elem.setAttribute('poll-id', message.media.poll.id);
|
||||
elem.setAttribute('message-id', '' + message.mid);
|
||||
return elem;
|
||||
}
|
||||
|
|
|
@ -31,9 +31,19 @@ export const formatDateAccordingToToday = (time: Date) => {
|
|||
return timeStr;
|
||||
};
|
||||
|
||||
export const getFullDate = (date: Date) => {
|
||||
return date.getDate() + ' ' + months[date.getMonth()] + ' ' + date.getFullYear() +
|
||||
', ' + ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2) + ':' + ('0' + date.getSeconds()).slice(-2);
|
||||
export const getFullDate = (date: Date, options: Partial<{
|
||||
noTime: true,
|
||||
noSeconds: true,
|
||||
monthAsNumber: true,
|
||||
leadingZero: true
|
||||
}> = {}) => {
|
||||
const joiner = options.monthAsNumber ? '.' : ' ';
|
||||
const time = ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2) + (options.noSeconds ? '' : ':' + ('0' + date.getSeconds()).slice(-2));
|
||||
|
||||
return (options.leadingZero ? ('0' + date.getDate()).slice(-2) : date.getDate()) +
|
||||
joiner + (options.monthAsNumber ? ('0' + (date.getMonth() + 1)).slice(-2) : months[date.getMonth()]) +
|
||||
joiner + date.getFullYear() +
|
||||
(options.noTime ? '' : ', ' + time);
|
||||
};
|
||||
|
||||
export function tsNow(seconds?: true) {
|
||||
|
|
|
@ -467,12 +467,14 @@ export function blurActiveElement() {
|
|||
}
|
||||
|
||||
export const CLICK_EVENT_NAME = isTouchSupported ? 'touchend' : 'click';
|
||||
export type AttachClickOptions = AddEventListenerOptions & Partial<{listenerSetter: ListenerSetter}>;
|
||||
export type AttachClickOptions = AddEventListenerOptions & Partial<{listenerSetter: ListenerSetter, touchMouseDown: true}>;
|
||||
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') {
|
||||
if(options.touchMouseDown && CLICK_EVENT_NAME === 'touchend') {
|
||||
add('mousedown', callback, options);
|
||||
} else if(CLICK_EVENT_NAME === 'touchend') {
|
||||
const o = {...options, once: true};
|
||||
|
||||
const onTouchStart = (e: TouchEvent) => {
|
||||
|
@ -600,7 +602,7 @@ export async function getFilesFromEvent(e: ClipboardEvent | DragEvent, onlyTypes
|
|||
const scanFiles = async(item: any) => {
|
||||
if(item.isDirectory) {
|
||||
const directoryReader = item.createReader();
|
||||
await new Promise((resolve, reject) => {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
directoryReader.readEntries(async(entries: any) => {
|
||||
for(const entry of entries) {
|
||||
await scanFiles(entry);
|
||||
|
|
|
@ -21,7 +21,7 @@ import appProfileManager from './appProfileManager';
|
|||
import appStickersManager from './appStickersManager';
|
||||
import appWebPagesManager from './appWebPagesManager';
|
||||
import { cancelEvent, getFilesFromEvent, placeCaretAtEnd } from '../../helpers/dom';
|
||||
import PopupNewMedia from '../../components/popupNewMedia';
|
||||
import PopupNewMedia from '../../components/popups/newMedia';
|
||||
import { TransitionSlider } from '../../components/transition';
|
||||
import { numberWithCommas } from '../../helpers/number';
|
||||
import MarkupTooltip from '../../components/chat/markupTooltip';
|
||||
|
@ -203,15 +203,15 @@ export class AppImManager {
|
|||
return;
|
||||
} else if(e.code == 'ArrowUp') {
|
||||
if(!chat.input.editMsgId) {
|
||||
const history = appMessagesManager.historiesStorage[chat.peerId];
|
||||
if(history?.history) {
|
||||
const history = appMessagesManager.getHistoryStorage(chat.peerId);
|
||||
if(history.history.length) {
|
||||
let goodMid: number;
|
||||
for(const mid of history.history) {
|
||||
const message = appMessagesManager.getMessageByPeer(chat.peerId, mid);
|
||||
const good = this.myId == chat.peerId ? message.fromId == this.myId : message.pFlags.out;
|
||||
|
||||
if(good) {
|
||||
if(appMessagesManager.canEditMessage(this.chat.peerId, mid, 'text')) {
|
||||
if(appMessagesManager.canEditMessage(this.chat.getMessage(mid), 'text')) {
|
||||
goodMid = mid;
|
||||
}
|
||||
|
||||
|
@ -235,6 +235,28 @@ export class AppImManager {
|
|||
|
||||
document.body.addEventListener('keydown', onKeyDown);
|
||||
|
||||
rootScope.addEventListener('history_multiappend', (e) => {
|
||||
const msgIdsByPeer = e.detail;
|
||||
|
||||
for(const peerId in msgIdsByPeer) {
|
||||
appSidebarRight.sharedMediaTab.renderNewMessages(+peerId, msgIdsByPeer[peerId]);
|
||||
}
|
||||
});
|
||||
|
||||
rootScope.addEventListener('history_delete', (e) => {
|
||||
const {peerId, msgs} = e.detail;
|
||||
|
||||
const mids = Object.keys(msgs).map(s => +s);
|
||||
appSidebarRight.sharedMediaTab.deleteDeletedMessages(peerId, mids);
|
||||
});
|
||||
|
||||
// Calls when message successfully sent and we have an id
|
||||
rootScope.addEventListener('message_sent', (e) => {
|
||||
const {storage, tempId, mid} = e.detail;
|
||||
const message = appMessagesManager.getMessageFromStorage(storage, mid);
|
||||
appSidebarRight.sharedMediaTab.renderNewMessages(message.peerId, [mid]);
|
||||
});
|
||||
|
||||
if(!isTouchSupported) {
|
||||
this.attachDragAndDropListeners();
|
||||
}
|
||||
|
@ -379,7 +401,7 @@ export class AppImManager {
|
|||
|
||||
const chatInput = this.chat.input;
|
||||
chatInput.willAttachType = attachType || (files[0].type.indexOf('image/') === 0 ? 'media' : "document");
|
||||
new PopupNewMedia(files, chatInput.willAttachType);
|
||||
new PopupNewMedia(this.chat, files, chatInput.willAttachType);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -406,7 +428,7 @@ export class AppImManager {
|
|||
}
|
||||
|
||||
private createNewChat() {
|
||||
const chat = new Chat(this, appChatsManager, appDocsManager, appInlineBotsManager, appMessagesManager, appPeersManager, appPhotosManager, appProfileManager, appStickersManager, appUsersManager, appWebPagesManager, appSidebarRight, appPollsManager, apiManager);
|
||||
const chat = new Chat(this, appChatsManager, appDocsManager, appInlineBotsManager, appMessagesManager, appPeersManager, appPhotosManager, appProfileManager, appStickersManager, appUsersManager, appWebPagesManager, appPollsManager, apiManager);
|
||||
|
||||
this.chats.push(chat);
|
||||
}
|
||||
|
@ -511,6 +533,10 @@ export class AppImManager {
|
|||
return this.setPeer(peerId, lastMsgId);
|
||||
}
|
||||
|
||||
public openScheduled(peerId: number) {
|
||||
this.setInnerPeer(peerId, undefined, 'scheduled');
|
||||
}
|
||||
|
||||
public async getPeerStatus(peerId: number) {
|
||||
let subtitle = '';
|
||||
if(!peerId) return subtitle;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -159,20 +159,21 @@ export class AppPollsManager {
|
|||
};
|
||||
}
|
||||
|
||||
public sendVote(peerId: number, messageId: number, optionIds: number[]): Promise<void> {
|
||||
const message = appMessagesManager.getMessageByPeer(peerId, messageId);
|
||||
public sendVote(message: any, optionIds: number[]): Promise<void> {
|
||||
const poll: Poll = message.media.poll;
|
||||
|
||||
const options: Uint8Array[] = optionIds.map(index => {
|
||||
return poll.answers[index].option;
|
||||
});
|
||||
|
||||
const messageId = message.mid;
|
||||
const peerId = message.peerId;
|
||||
const inputPeer = appPeersManager.getInputPeerById(peerId);
|
||||
|
||||
if(messageId < 0) {
|
||||
return appMessagesManager.invokeAfterMessageIsSent(messageId, 'sendVote', (mid) => {
|
||||
return appMessagesManager.invokeAfterMessageIsSent(messageId, 'sendVote', (message) => {
|
||||
this.log('invoke sendVote callback');
|
||||
return this.sendVote(peerId, mid, optionIds);
|
||||
return this.sendVote(message, optionIds);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -186,33 +187,22 @@ export class AppPollsManager {
|
|||
});
|
||||
}
|
||||
|
||||
public getResults(peerId: number, messageId: number) {
|
||||
const message = appMessagesManager.getMessageByPeer(peerId, messageId);
|
||||
public getResults(message: any) {
|
||||
const inputPeer = appPeersManager.getInputPeerById(message.peerId);
|
||||
|
||||
return apiManager.invokeApi('messages.getPollResults', {
|
||||
peer: inputPeer,
|
||||
msg_id: messageId
|
||||
msg_id: message.mid
|
||||
}).then(updates => {
|
||||
apiUpdatesManager.processUpdateMessage(updates);
|
||||
this.log('getResults updates:', updates);
|
||||
});
|
||||
}
|
||||
|
||||
public getVotes(peerId: number, messageId: number, option?: Uint8Array, offset?: string, limit = 20) {
|
||||
let flags = 0;
|
||||
if(option) {
|
||||
flags |= 1 << 0;
|
||||
}
|
||||
|
||||
if(offset) {
|
||||
flags |= 1 << 1;
|
||||
}
|
||||
|
||||
public getVotes(message: any, option?: Uint8Array, offset?: string, limit = 20) {
|
||||
return apiManager.invokeApi('messages.getPollVotes', {
|
||||
flags,
|
||||
peer: appPeersManager.getInputPeerById(peerId),
|
||||
id: messageId,
|
||||
peer: appPeersManager.getInputPeerById(message.peerId),
|
||||
id: message.mid,
|
||||
option,
|
||||
offset,
|
||||
limit
|
||||
|
@ -225,15 +215,14 @@ export class AppPollsManager {
|
|||
});
|
||||
}
|
||||
|
||||
public stopPoll(peerId: number, messageId: number) {
|
||||
const message = appMessagesManager.getMessageByPeer(peerId, messageId);
|
||||
public stopPoll(message: any) {
|
||||
const poll: Poll = message.media.poll;
|
||||
|
||||
if(poll.pFlags.closed) return Promise.resolve();
|
||||
|
||||
const newPoll = copy(poll);
|
||||
newPoll.pFlags.closed = true;
|
||||
return appMessagesManager.editMessage(peerId, messageId, undefined, {
|
||||
return appMessagesManager.editMessage(message, undefined, {
|
||||
newMedia: this.getInputMediaPoll(newPoll)
|
||||
}).then(() => {
|
||||
//console.log('stopped poll');
|
||||
|
|
|
@ -19,8 +19,6 @@ export function logger(prefix: string, level = LogLevels.log | LogLevels.warn |
|
|||
|
||||
//level = LogLevels.log | LogLevels.warn | LogLevels.error | LogLevels.debug
|
||||
|
||||
prefix = '[' + prefix + ']:';
|
||||
|
||||
function Log(...args: any[]) {
|
||||
return level & LogLevels.log && console.log(dT(), prefix, ...args);
|
||||
}
|
||||
|
@ -48,6 +46,12 @@ export function logger(prefix: string, level = LogLevels.log | LogLevels.warn |
|
|||
Log.debug = function(...args: any[]) {
|
||||
return level & LogLevels.debug && console.debug(dT(), prefix, ...args);
|
||||
};
|
||||
|
||||
Log.setPrefix = function(_prefix: string) {
|
||||
prefix = '[' + _prefix + ']:';
|
||||
};
|
||||
|
||||
Log.setPrefix(prefix);
|
||||
|
||||
return Log;
|
||||
};
|
|
@ -183,7 +183,7 @@ export class ApiManager {
|
|||
}
|
||||
|
||||
const networkers = cache[dcId];
|
||||
if(networkers.length >= /* 1 */(connectionType == 'client' ? 1 : 3)) {
|
||||
if(networkers.length >= /* 1 */(connectionType !== 'download' ? 1 : 3)) {
|
||||
const networker = networkers.pop();
|
||||
networkers.unshift(networker);
|
||||
return Promise.resolve(networker);
|
||||
|
|
|
@ -1148,7 +1148,7 @@ namespace RichTextProcessor {
|
|||
}
|
||||
|
||||
export function matchUrl(text: string) {
|
||||
return text.match(urlRegExp);
|
||||
return !text ? null : text.match(urlRegExp);
|
||||
}
|
||||
|
||||
/* const el = document.createElement('span');
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import type { StickerSet, Update } from "../layer";
|
||||
import type { MyDocument } from "./appManagers/appDocsManager";
|
||||
import type { AppMessagesManager, Dialog } from "./appManagers/appMessagesManager";
|
||||
import type { AppMessagesManager, Dialog, MessagesStorage } from "./appManagers/appMessagesManager";
|
||||
import type { Poll, PollResults } from "./appManagers/appPollsManager";
|
||||
import type { MyDialogFilter } from "./storages/filters";
|
||||
import type { ConnectionStatusChange } from "../types";
|
||||
|
@ -30,17 +30,17 @@ type BroadcastEvents = {
|
|||
'dialogs_archived_unread': {count: number},
|
||||
|
||||
'history_append': {peerId: number, messageId: number, my?: boolean},
|
||||
'history_update': {peerId: number, mid: number},
|
||||
'history_update': {storage: MessagesStorage, peerId: number, mid: number},
|
||||
'history_reply_markup': {peerId: number},
|
||||
'history_multiappend': AppMessagesManager['newMessagesToHandle'],
|
||||
'history_delete': {peerId: number, msgs: {[mid: number]: true}},
|
||||
'history_forbidden': number,
|
||||
'history_reload': number,
|
||||
'history_request': void,
|
||||
//'history_request': void,
|
||||
|
||||
'message_edit': {peerId: number, mid: number, justMedia: boolean},
|
||||
'message_edit': {storage: MessagesStorage, peerId: number, mid: number},
|
||||
'message_views': {mid: number, views: number},
|
||||
'message_sent': {tempId: number, mid: number},
|
||||
'message_sent': {storage: MessagesStorage, tempId: number, tempMessage: any, mid: number},
|
||||
'messages_pending': void,
|
||||
'messages_read': void,
|
||||
'messages_downloaded': number[],
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { putPreloader } from '../components/misc';
|
||||
import PopupAvatar from '../components/popupAvatar';
|
||||
import PopupAvatar from '../components/popups/avatar';
|
||||
import appStateManager from '../lib/appManagers/appStateManager';
|
||||
//import apiManager from '../lib/mtproto/apiManager';
|
||||
import apiManager from '../lib/mtproto/mtprotoworker';
|
||||
|
|
|
@ -178,12 +178,33 @@ $chat-helper-size: 39px;
|
|||
height: 24px;
|
||||
}
|
||||
|
||||
&.send {
|
||||
.tgico-send {
|
||||
color: $color-blue !important;
|
||||
}
|
||||
|
||||
.tgico-check {
|
||||
color: $color-blue !important;
|
||||
height: 32px!important;
|
||||
font-size: 2rem;
|
||||
|
||||
&:before {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.tgico-schedule {
|
||||
background-color: $color-blue;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
line-height: 38px;
|
||||
}
|
||||
|
||||
&.send .tgico-send,
|
||||
&.record .tgico-microphone2 {
|
||||
&.record .tgico-microphone2,
|
||||
&.edit .tgico-check,
|
||||
&.schedule .tgico-schedule {
|
||||
animation: grow-icon .4s forwards ease-in-out;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
.popup-new-media {
|
||||
user-select: none;
|
||||
$parent: ".popup";
|
||||
|
||||
#{$parent} {
|
||||
|
@ -54,6 +55,7 @@
|
|||
align-items: center;
|
||||
margin-bottom: 9px;
|
||||
padding: 12px 20px 15px;
|
||||
position: relative;
|
||||
|
||||
.btn-primary {
|
||||
width: 79px;
|
||||
|
@ -148,6 +150,15 @@
|
|||
font-size: inherit;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-menu-overlay {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.menu-send {
|
||||
z-index: 4;
|
||||
top: calc(100% + .25rem);
|
||||
}
|
||||
}
|
||||
|
||||
.popup-new-media.popup-send-photo {
|
||||
|
|
Loading…
Reference in New Issue
Block a user