Browse Source

Popup changes:

Closing by escape
Avatar & new media are now popups
Fix deleting message
Edit single item from album
Fix message text in album
Fix overflow in pinned subtitle
master
morethanwords 4 years ago
parent
commit
1b6fc2622e
  1. 37
      src/components/chat/contextMenu.ts
  2. 272
      src/components/chat/input.ts
  3. 50
      src/components/chat/messageRender.ts
  4. 13
      src/components/inputField.ts
  5. 28
      src/components/popup.ts
  6. 64
      src/components/popupAvatar.ts
  7. 31
      src/components/popupCreatePoll.ts
  8. 285
      src/components/popupNewMedia.ts
  9. 14
      src/components/sidebarLeft/tabs/editProfile.ts
  10. 8
      src/components/sidebarLeft/tabs/newChannel.ts
  11. 12
      src/components/sidebarLeft/tabs/newGroup.ts
  12. 27
      src/index.hbs
  13. 79
      src/lib/appManagers/appImManager.ts
  14. 1
      src/lib/rootScope.ts
  15. 8
      src/pages/pageSignUp.ts
  16. 22
      src/scss/partials/_chat.scss

37
src/components/chat/contextMenu.ts

@ -45,9 +45,16 @@ export class ChatContextMenu {
if(!msgID) return; if(!msgID) return;
this.peerID = $rootScope.selectedPeerID; this.peerID = $rootScope.selectedPeerID;
this.msgID = msgID; //this.msgID = msgID;
this.target = e.target as HTMLElement; this.target = e.target as HTMLElement;
const albumItem = findUpClassName(this.target, 'album-item');
if(albumItem) {
this.msgID = +albumItem.dataset.mid;
} else {
this.msgID = msgID;
}
this.buttons.forEach(button => { this.buttons.forEach(button => {
const good = button.verify(this.peerID, this.msgID, this.target); const good = button.verify(this.peerID, this.msgID, this.target);
button.element.classList.toggle('hide', !good); button.element.classList.toggle('hide', !good);
@ -145,13 +152,13 @@ export class ChatContextMenu {
}; };
private onCopyClick = () => { private onCopyClick = () => {
let message = appMessagesManager.getMessage(this.msgID); const message = appMessagesManager.getMessage(this.msgID);
let str = message ? message.message : ''; const str = message ? message.message : '';
var textArea = document.createElement("textarea"); const textArea = document.createElement('textarea');
textArea.value = str; textArea.value = str;
textArea.style.position = "fixed"; //avoid scrolling to bottom textArea.style.position = 'fixed'; //avoid scrolling to bottom
document.body.appendChild(textArea); document.body.appendChild(textArea);
textArea.focus(); textArea.focus();
textArea.select(); textArea.select();
@ -182,22 +189,16 @@ export class ChatContextMenu {
}; };
private onForwardClick = () => { private onForwardClick = () => {
let msgID: number; appSidebarRight.forwardTab.open([this.msgID]);
const albumItem = findUpClassName(this.target, 'album-item');
if(albumItem) {
msgID = +albumItem.dataset.mid;
}
appSidebarRight.forwardTab.open([msgID]);
}; };
private onDeleteClick = () => { private onDeleteClick = () => {
let peerID = $rootScope.selectedPeerID; const peerID = $rootScope.selectedPeerID;
let firstName = appPeersManager.getPeerTitle(peerID, false, true); const firstName = appPeersManager.getPeerTitle(peerID, false, true);
let callback = (revoke: boolean) => { const msgID = this.msgID;
appMessagesManager.deleteMessages([this.msgID], revoke); const callback = (revoke: boolean) => {
appMessagesManager.deleteMessages([msgID], revoke);
}; };
let title: string, description: string, buttons: PopupButton[]; let title: string, description: string, buttons: PopupButton[];
@ -237,7 +238,7 @@ export class ChatContextMenu {
isCancel: true isCancel: true
}); });
let popup = new PopupPeer('popup-delete-chat', { const popup = new PopupPeer('popup-delete-chat', {
peerID: peerID, peerID: peerID,
title: title, title: title,
description: description, description: description,

272
src/components/chat/input.ts

@ -10,15 +10,15 @@ import apiManager from "../../lib/mtproto/mtprotoworker";
import opusDecodeController from "../../lib/opusDecodeController"; import opusDecodeController from "../../lib/opusDecodeController";
import { RichTextProcessor } from "../../lib/richtextprocessor"; import { RichTextProcessor } from "../../lib/richtextprocessor";
import $rootScope from '../../lib/rootScope'; import $rootScope from '../../lib/rootScope';
import { calcImageInBox, cancelEvent, getRichValue } from "../../lib/utils"; import { cancelEvent, getRichValue } from "../../lib/utils";
import ButtonMenu, { ButtonMenuItemOptions } from '../buttonMenu'; import ButtonMenu, { ButtonMenuItemOptions } from '../buttonMenu';
import emoticonsDropdown from "../emoticonsDropdown"; import emoticonsDropdown from "../emoticonsDropdown";
import { Layouter, RectPart } from "../groupedLayout";
import PopupCreatePoll from "../popupCreatePoll"; import PopupCreatePoll from "../popupCreatePoll";
import PopupNewMedia from '../popupNewMedia';
import { ripple } from '../ripple'; import { ripple } from '../ripple';
import Scrollable from "../scrollable"; import Scrollable from "../scrollable";
import { toast } from "../toast"; import { toast } from "../toast";
import { wrapDocument, wrapReply } from "../wrappers"; import { wrapReply } from "../wrappers";
const RECORD_MIN_TIME = 500; const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.'; const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@ -40,14 +40,6 @@ export class ChatInput {
public attachMenu: HTMLButtonElement; public attachMenu: HTMLButtonElement;
private attachMenuButtons: (ButtonMenuItemOptions & {verify: (peerID: number) => boolean})[]; private attachMenuButtons: (ButtonMenuItemOptions & {verify: (peerID: number) => boolean})[];
public attachMediaPopUp: {
container?: HTMLDivElement,
titleEl?: HTMLDivElement,
sendBtn?: HTMLButtonElement,
mediaContainer?: HTMLDivElement,
captionInput?: HTMLInputElement
} = {};
public replyElements: { public replyElements: {
container?: HTMLDivElement, container?: HTMLDivElement,
cancelBtn?: HTMLButtonElement, cancelBtn?: HTMLButtonElement,
@ -74,12 +66,14 @@ export class ChatInput {
constructor() { constructor() {
this.attachMenu = document.getElementById('attach-file') as HTMLButtonElement; this.attachMenu = document.getElementById('attach-file') as HTMLButtonElement;
let willAttachType: 'document' | 'media';
this.attachMenuButtons = [{ this.attachMenuButtons = [{
icon: 'photo', icon: 'photo',
text: 'Photo or Video', text: 'Photo or Video',
onClick: () => { onClick: () => {
this.fileInput.value = '';
this.fileInput.setAttribute('accept', 'image/*, video/*'); this.fileInput.setAttribute('accept', 'image/*, video/*');
willAttach.type = 'media'; willAttachType = 'media';
this.fileInput.click(); this.fileInput.click();
}, },
verify: (peerID: number) => peerID > 0 || appChatsManager.hasRights(peerID, 'send', 'send_media') verify: (peerID: number) => peerID > 0 || appChatsManager.hasRights(peerID, 'send', 'send_media')
@ -87,8 +81,9 @@ export class ChatInput {
icon: 'document', icon: 'document',
text: 'Document', text: 'Document',
onClick: () => { onClick: () => {
this.fileInput.value = '';
this.fileInput.removeAttribute('accept'); this.fileInput.removeAttribute('accept');
willAttach.type = 'document'; willAttachType = 'document';
this.fileInput.click(); this.fileInput.click();
}, },
verify: (peerID: number) => peerID > 0 || appChatsManager.hasRights(peerID, 'send', 'send_media') verify: (peerID: number) => peerID > 0 || appChatsManager.hasRights(peerID, 'send', 'send_media')
@ -116,12 +111,6 @@ export class ChatInput {
ripple(this.attachMenu); ripple(this.attachMenu);
this.attachMediaPopUp.container = this.pageEl.querySelector('.popup-send-photo') as HTMLDivElement;
this.attachMediaPopUp.titleEl = this.attachMediaPopUp.container.querySelector('.popup-title') as HTMLDivElement;
this.attachMediaPopUp.sendBtn = this.attachMediaPopUp.container.querySelector('.btn-primary') as HTMLButtonElement;
this.attachMediaPopUp.mediaContainer = this.attachMediaPopUp.container.querySelector('.popup-photo') as HTMLDivElement;
this.attachMediaPopUp.captionInput = this.attachMediaPopUp.container.querySelector('input') as HTMLInputElement;
this.replyElements.container = this.pageEl.querySelector('.reply-wrapper') as HTMLDivElement; this.replyElements.container = this.pageEl.querySelector('.reply-wrapper') as HTMLDivElement;
this.replyElements.cancelBtn = this.replyElements.container.querySelector('.reply-cancel') as HTMLButtonElement; this.replyElements.cancelBtn = this.replyElements.container.querySelector('.reply-cancel') as HTMLButtonElement;
this.replyElements.titleEl = this.replyElements.container.querySelector('.reply-title') as HTMLDivElement; this.replyElements.titleEl = this.replyElements.container.querySelector('.reply-title') as HTMLDivElement;
@ -281,210 +270,19 @@ export class ChatInput {
window.document.execCommand('insertHTML', false, text); window.document.execCommand('insertHTML', false, text);
}); });
let attachFile = (file: File) => {
return new Promise<HTMLDivElement>((resolve, reject) => {
let params: SendFileParams = {};
params.file = file;
//console.log('selected file:', file, typeof(file), willAttach);
let itemDiv = document.createElement('div');
switch(willAttach.type) {
case 'media': {
let isVideo = file.type.indexOf('video/') === 0;
itemDiv.classList.add('popup-item-media');
if(isVideo) {
let video = document.createElement('video');
let source = document.createElement('source');
source.src = params.objectURL = URL.createObjectURL(file);
video.autoplay = false;
video.controls = false;
video.muted = true;
video.setAttribute('playsinline', '');
video.onloadeddata = () => {
params.width = video.videoWidth;
params.height = video.videoHeight;
params.duration = Math.floor(video.duration);
itemDiv.append(video);
resolve(itemDiv);
};
video.append(source);
} else {
let img = new Image();
img.src = params.objectURL = URL.createObjectURL(file);
img.onload = () => {
params.width = img.naturalWidth;
params.height = img.naturalHeight;
itemDiv.append(img);
resolve(itemDiv);
};
}
break;
}
case 'document': {
const isPhoto = file.type.indexOf('image/') !== -1;
if(isPhoto) {
params.objectURL = URL.createObjectURL(file);
}
let docDiv = wrapDocument({
file: file,
file_name: file.name || '',
size: file.size,
type: isPhoto ? 'photo' : 'doc',
url: params.objectURL
} as any, false, true);
const finish = () => {
itemDiv.append(docDiv);
resolve(itemDiv);
};
if(isPhoto) {
let img = new Image();
img.src = params.objectURL;
img.onload = () => {
params.width = img.naturalWidth;
params.height = img.naturalHeight;
finish();
};
img.onerror = finish;
} else {
finish();
}
break;
}
}
willAttach.sendFileDetails.push(params);
});
};
let attachFiles = (files: File[]) => {
this.fileInput.value = '';
let container = this.attachMediaPopUp.container.firstElementChild as HTMLElement;
container.classList.remove('is-media', 'is-document', 'is-album');
this.attachMediaPopUp.captionInput.value = '';
this.attachMediaPopUp.mediaContainer.innerHTML = '';
this.attachMediaPopUp.mediaContainer.style.width = this.attachMediaPopUp.mediaContainer.style.height = '';
//willAttach.sendFileDetails.length = 0;
willAttach.sendFileDetails = []; // need new array
files = files.filter(file => {
if(willAttach.type == 'media') {
return ['image/', 'video/'].find(s => file.type.indexOf(s) === 0);
} else {
return true;
}
});
if(files.length) {
if(willAttach.type == 'document') {
this.attachMediaPopUp.titleEl.innerText = 'Send ' + (files.length > 1 ? files.length + ' Files' : 'File');
container.classList.add('is-document');
} else {
container.classList.add('is-media');
let foundPhotos = 0;
let foundVideos = 0;
files.forEach(file => {
if(file.type.indexOf('image/') === 0) ++foundPhotos;
else if(file.type.indexOf('video/') === 0) ++foundVideos;
});
if(foundPhotos && foundVideos) {
this.attachMediaPopUp.titleEl.innerText = 'Send Album';
} else if(foundPhotos) {
this.attachMediaPopUp.titleEl.innerText = 'Send ' + (foundPhotos > 1 ? foundPhotos + ' Photos' : 'Photo');
} else if(foundVideos) {
this.attachMediaPopUp.titleEl.innerText = 'Send ' + (foundVideos > 1 ? foundVideos + ' Videos' : 'Video');
}
}
}
Promise.all(files.map(attachFile)).then(results => {
if(willAttach.type == 'media') {
if(willAttach.sendFileDetails.length > 1) {
container.classList.add('is-album');
let layouter = new Layouter(willAttach.sendFileDetails.map(o => ({w: o.width, h: o.height})), 380, 100, 4);
let layout = layouter.layout();
for(let {geometry, sides} of layout) {
let div = results.shift();
div.style.width = geometry.width + 'px';
div.style.height = geometry.height + 'px';
div.style.top = geometry.y + 'px';
div.style.left = geometry.x + 'px';
if(sides & RectPart.Right) {
this.attachMediaPopUp.mediaContainer.style.width = geometry.width + geometry.x + 'px';
}
if(sides & RectPart.Bottom) {
this.attachMediaPopUp.mediaContainer.style.height = geometry.height + geometry.y + 'px';
}
this.attachMediaPopUp.mediaContainer.append(div);
}
//console.log('chatInput album layout:', layout);
} else {
let params = willAttach.sendFileDetails[0];
let div = results[0];
let {w, h} = calcImageInBox(params.width, params.height, 380, 320);
div.style.width = w + 'px';
div.style.height = h + 'px';
this.attachMediaPopUp.mediaContainer.append(div);
}
} else {
this.attachMediaPopUp.mediaContainer.append(...results);
}
this.attachMediaPopUp.container.classList.add('active');
});
};
type SendFileParams = Partial<{
file: File,
objectURL: string,
width: number,
height: number,
duration: number
}>;
let willAttach: Partial<{
type: 'media' | 'document',
isMedia: boolean,
sendFileDetails: SendFileParams[]
}> = {
sendFileDetails: []
};
this.fileInput.addEventListener('change', (e) => { this.fileInput.addEventListener('change', (e) => {
let files = (e.target as HTMLInputElement & EventTarget).files; let files = (e.target as HTMLInputElement & EventTarget).files;
if(!files.length) { if(!files.length) {
return; return;
} }
attachFiles(Array.from(files)); new PopupNewMedia(Array.from(files).slice(), willAttachType);
this.fileInput.value = '';
}, false); }, false);
document.addEventListener('paste', (event) => { document.addEventListener('paste', (event) => {
const peerID = $rootScope.selectedPeerID; const peerID = $rootScope.selectedPeerID;
if(!peerID || this.attachMediaPopUp.container.classList.contains('active') || (peerID < 0 && !appChatsManager.hasRights(peerID, 'send', 'send_media'))) { if(!peerID || $rootScope.overlayIsActive || (peerID < 0 && !appChatsManager.hasRights(peerID, 'send', 'send_media'))) {
return; return;
} }
@ -503,56 +301,12 @@ export class ChatInput {
//console.log(items[i], file); //console.log(items[i], file);
if(!file) continue; if(!file) continue;
willAttach.type = file.type.indexOf('image/') === 0 ? 'media' : "document"; willAttachType = file.type.indexOf('image/') === 0 ? 'media' : "document";
attachFiles([file]); new PopupNewMedia([file], willAttachType);
} }
} }
}, true); }, true);
this.attachMediaPopUp.sendBtn.addEventListener('click', () => {
this.attachMediaPopUp.container.classList.remove('active');
let caption = this.attachMediaPopUp.captionInput.value;
willAttach.isMedia = willAttach.type == 'media';
//console.log('will send files with options:', willAttach);
let peerID = appImManager.peerID;
if(willAttach.sendFileDetails.length > 1 && willAttach.isMedia) {
appMessagesManager.sendAlbum(peerID, willAttach.sendFileDetails.map(d => d.file), Object.assign({
caption,
replyToMsgID: this.replyToMsgID
}, willAttach));
} else {
if(caption) {
if(willAttach.sendFileDetails.length > 1) {
appMessagesManager.sendText(peerID, caption, {replyToMsgID: this.replyToMsgID});
caption = '';
this.replyToMsgID = 0;
}
}
let promises = willAttach.sendFileDetails.map(params => {
let promise = appMessagesManager.sendFile(peerID, params.file, Object.assign({
//isMedia: willAttach.isMedia,
isMedia: params.file.type.includes('audio/') || willAttach.isMedia,
caption,
replyToMsgID: this.replyToMsgID
}, params));
caption = '';
this.replyToMsgID = 0;
return promise;
});
}
//Promise.all(promises);
//appMessagesManager.sendFile(appImManager.peerID, willAttach.file, willAttach);
this.onMessageSent();
});
const onBtnSendClick = (e: Event) => { const onBtnSendClick = (e: Event) => {
cancelEvent(e); cancelEvent(e);

50
src/components/chat/messageRender.ts

@ -0,0 +1,50 @@
import { formatNumber } from "../../lib/utils";
type Message = any;
export namespace MessageRender {
/* export const setText = () => {
}; */
export const setTime = (message: Message, bubble: HTMLElement, bubbleContainer: HTMLElement, messageDiv: HTMLElement) => {
let date = new Date(message.date * 1000);
let time = ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2);
if(message.views) {
bubble.classList.add('channel-post');
time = formatNumber(message.views, 1) + ' <i class="tgico-channelviews"></i> ' + time;
if(!message.savedFrom) {
let forward = document.createElement('div');
forward.classList.add('bubble-beside-button', 'forward');
forward.innerHTML = `
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24">
<defs>
<path d="M13.55 3.24L13.64 3.25L13.73 3.27L13.81 3.29L13.9 3.32L13.98 3.35L14.06 3.39L14.14 3.43L14.22 3.48L14.29 3.53L14.36 3.59L14.43 3.64L22.23 10.85L22.36 10.99L22.48 11.15L22.57 11.31L22.64 11.48L22.69 11.66L22.72 11.85L22.73 12.04L22.71 12.22L22.67 12.41L22.61 12.59L22.53 12.76L22.42 12.93L22.29 13.09L22.23 13.15L14.43 20.36L14.28 20.48L14.12 20.58L13.95 20.66L13.77 20.72L13.58 20.76L13.4 20.77L13.22 20.76L13.03 20.73L12.85 20.68L12.68 20.61L12.52 20.52L12.36 20.4L12.22 20.27L12.16 20.2L12.1 20.13L12.05 20.05L12.01 19.98L11.96 19.9L11.93 19.82L11.89 19.73L11.87 19.65L11.84 19.56L11.83 19.47L11.81 19.39L11.81 19.3L11.8 19.2L11.8 16.42L11 16.49L10.23 16.58L9.51 16.71L8.82 16.88L8.18 17.09L7.57 17.33L7.01 17.6L6.48 17.91L5.99 18.26L5.55 18.64L5.14 19.05L4.77 19.51L4.43 19.99L4.29 20.23L4.21 20.35L4.11 20.47L4 20.57L3.88 20.65L3.75 20.72L3.62 20.78L3.48 20.82L3.33 20.84L3.19 20.84L3.04 20.83L2.9 20.79L2.75 20.74L2.62 20.68L2.53 20.62L2.45 20.56L2.38 20.5L2.31 20.43L2.25 20.36L2.2 20.28L2.15 20.19L2.11 20.11L2.07 20.02L2.04 19.92L2.02 19.83L2.01 19.73L2 19.63L2.04 17.99L2.19 16.46L2.46 15.05L2.85 13.75L3.35 12.58L3.97 11.53L4.7 10.6L5.55 9.8L6.51 9.12L7.59 8.56L8.77 8.13L10.07 7.83L11.48 7.65L11.8 7.63L11.8 4.8L11.91 4.56L12.02 4.35L12.14 4.16L12.25 3.98L12.37 3.82L12.48 3.68L12.61 3.56L12.73 3.46L12.85 3.38L12.98 3.31L13.11 3.27L13.24 3.24L13.37 3.23L13.46 3.23L13.55 3.24Z" id="b13RmHDQtl"></path>
</defs>
<use xlink:href="#b13RmHDQtl" opacity="1" fill="#fff" fill-opacity="1"></use>
</svg>`;
bubbleContainer.append(forward);
bubble.classList.add('with-beside-button');
}
}
if(message.edit_date) {
bubble.classList.add('is-edited');
time = '<i class="edited">edited</i> ' + time;
}
let timeSpan = document.createElement('span');
timeSpan.classList.add('time');
let timeInner = document.createElement('div');
timeInner.classList.add('inner', 'tgico');
timeInner.innerHTML = time;
timeSpan.appendChild(timeInner);
messageDiv.append(timeSpan);
return timeSpan;
};
}

13
src/components/inputField.ts

@ -0,0 +1,13 @@
const InputField = (placeholder: string, label: string, name: string) => {
const div = document.createElement('div');
div.classList.add('input-field');
div.innerHTML = `
<input type="text" name="${name}" id="input-${name}" placeholder="${placeholder}" autocomplete="off" required="">
<label for="input-${name}">${label}</label>
`;
return div;
};
export default InputField;

28
src/components/popup.ts

@ -1,3 +1,5 @@
import $rootScope from "../lib/rootScope";
import { cancelEvent } from "../lib/utils";
import AvatarElement from "./avatar"; import AvatarElement from "./avatar";
import { ripple } from "./ripple"; import { ripple } from "./ripple";
@ -12,6 +14,7 @@ export class PopupElement {
protected onClose: () => void; protected onClose: () => void;
protected onCloseAfterTimeout: () => void; protected onCloseAfterTimeout: () => void;
protected onEscape: () => boolean = () => true;
constructor(className: string, buttons?: Array<PopupButton>, options: Partial<{closable: boolean, withConfirm: string, body: boolean}> = {}) { constructor(className: string, buttons?: Array<PopupButton>, options: Partial<{closable: boolean, withConfirm: string, body: boolean}> = {}) {
this.element.classList.add('popup'); this.element.classList.add('popup');
@ -26,14 +29,14 @@ export class PopupElement {
if(options.closable) { if(options.closable) {
this.closeBtn = document.createElement('span'); this.closeBtn = document.createElement('span');
this.closeBtn.classList.add('btn-icon', 'popup-close', 'tgico-close'); this.closeBtn.classList.add('btn-icon', 'popup-close', 'tgico-close');
ripple(this.closeBtn); //ripple(this.closeBtn);
this.header.prepend(this.closeBtn); this.header.prepend(this.closeBtn);
this.closeBtn.addEventListener('click', () => { this.closeBtn.addEventListener('click', this.destroy, {once: true});
this.destroy();
}, {once: true});
} }
window.addEventListener('keydown', this._onKeyDown, {capture: true});
if(options.withConfirm) { if(options.withConfirm) {
this.confirmBtn = document.createElement('button'); this.confirmBtn = document.createElement('button');
this.confirmBtn.classList.add('btn-primary'); this.confirmBtn.classList.add('btn-primary');
@ -80,20 +83,33 @@ export class PopupElement {
this.element.append(this.container); this.element.append(this.container);
} }
private _onKeyDown = (e: KeyboardEvent) => {
if(e.key == 'Escape' && this.onEscape()) {
cancelEvent(e);
this.destroy();
}
};
public show() { public show() {
document.body.append(this.element); document.body.append(this.element);
void this.element.offsetWidth; // reflow void this.element.offsetWidth; // reflow
this.element.classList.add('active'); this.element.classList.add('active');
$rootScope.overlayIsActive = true;
} }
public destroy() { public destroy = () => {
this.onClose && this.onClose(); this.onClose && this.onClose();
this.element.classList.remove('active'); this.element.classList.remove('active');
window.removeEventListener('keydown', this._onKeyDown, {capture: true});
if(this.closeBtn) this.closeBtn.removeEventListener('click', this.destroy);
$rootScope.overlayIsActive = false;
setTimeout(() => { setTimeout(() => {
this.element.remove(); this.element.remove();
this.onCloseAfterTimeout && this.onCloseAfterTimeout(); this.onCloseAfterTimeout && this.onCloseAfterTimeout();
}, 1000); }, 1000);
} };
} }
export type PopupButton = { export type PopupButton = {

64
src/components/popupAvatar.ts

@ -1,11 +1,14 @@
import resizeableImage from "../lib/cropper";
import appDownloadManager from "../lib/appManagers/appDownloadManager"; import appDownloadManager from "../lib/appManagers/appDownloadManager";
import resizeableImage from "../lib/cropper";
import { PopupElement } from "./popup";
import { ripple } from "./ripple";
export default class PopupAvatar extends PopupElement {
private cropContainer: HTMLElement;
private input: HTMLInputElement;
private btnSubmit: HTMLElement;
private h6: HTMLElement;
export class PopupAvatar {
private container = document.getElementById('popup-avatar');
private input = this.container.querySelector('input') as HTMLInputElement;
private cropContainer = this.container.querySelector('.crop') as HTMLDivElement;
private closeBtn = this.container.querySelector('.popup-close') as HTMLButtonElement;
private image = new Image(); private image = new Image();
private canvas: HTMLCanvasElement; private canvas: HTMLCanvasElement;
@ -18,18 +21,31 @@ export class PopupAvatar {
private onCrop: (upload: () => ReturnType<typeof appDownloadManager.upload>) => void; private onCrop: (upload: () => ReturnType<typeof appDownloadManager.upload>) => void;
constructor() { constructor() {
this.container.style.display = ''; // need for no blink super('popup-avatar', null, {closable: true});
this.h6 = document.createElement('h6');
this.h6.innerText = 'Drag to Reposition';
this.closeBtn.classList.remove('btn-icon');
this.header.append(this.h6);
this.cropContainer = document.createElement('div');
this.cropContainer.classList.add('crop');
this.cropContainer.append(this.image); this.cropContainer.append(this.image);
this.input = document.createElement('input');
this.input.type = 'file';
this.input.style.display = 'none';
this.input.addEventListener('change', (e: any) => { this.input.addEventListener('change', (e: any) => {
var file = e.target.files[0]; const file = e.target.files[0];
if(!file) { if(!file) {
return; return;
} }
var reader = new FileReader(); const reader = new FileReader();
reader.onload = (e) => { reader.onload = (e) => {
var contents = e.target.result as string; const contents = e.target.result as string;
this.image = new Image(); this.image = new Image();
this.cropContainer.append(this.image); this.cropContainer.append(this.image);
@ -39,9 +55,7 @@ export class PopupAvatar {
/* let {w, h} = calcImageInBox(this.image.naturalWidth, this.image.naturalHeight, 460, 554); /* let {w, h} = calcImageInBox(this.image.naturalWidth, this.image.naturalHeight, 460, 554);
cropContainer.style.width = w + 'px'; cropContainer.style.width = w + 'px';
cropContainer.style.height = h + 'px'; */ cropContainer.style.height = h + 'px'; */
this.container.classList.remove('hide'); this.show();
void this.container.offsetWidth; // reflow
this.container.classList.add('active');
this.cropper = resizeableImage(this.image, this.canvas); this.cropper = resizeableImage(this.image, this.canvas);
this.input.value = ''; this.input.value = '';
@ -51,8 +65,10 @@ export class PopupAvatar {
reader.readAsDataURL(file); reader.readAsDataURL(file);
}, false); }, false);
// apply this.btnSubmit = document.createElement('button');
this.container.querySelector('.btn-crop').addEventListener('click', () => { this.btnSubmit.className = 'btn-primary btn-circle btn-crop btn-icon tgico-check z-depth-1';
ripple(this.btnSubmit);
this.btnSubmit.addEventListener('click', () => {
this.cropper.crop(); this.cropper.crop();
this.closeBtn.click(); this.closeBtn.click();
@ -63,16 +79,14 @@ export class PopupAvatar {
}, 'image/jpeg', 1); }, 'image/jpeg', 1);
}); });
this.closeBtn.addEventListener('click', () => { this.container.append(this.cropContainer, this.btnSubmit, this.input);
setTimeout(() => {
this.cropper.removeHandlers();
if(this.image) {
this.image.remove();
}
this.container.classList.add('hide'); this.onCloseAfterTimeout = () => {
}, 200); this.cropper.removeHandlers();
}); if(this.image) {
this.image.remove();
}
};
} }
private resolve() { private resolve() {
@ -94,5 +108,3 @@ export class PopupAvatar {
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height); ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
} }
} }
export default new PopupAvatar();

31
src/components/popupCreatePoll.ts

@ -4,23 +4,12 @@ import appPollsManager, { Poll } from "../lib/appManagers/appPollsManager";
import $rootScope from "../lib/rootScope"; import $rootScope from "../lib/rootScope";
import { findUpTag, whichChild } from "../lib/utils"; import { findUpTag, whichChild } from "../lib/utils";
import CheckboxField from "./checkbox"; import CheckboxField from "./checkbox";
import InputField from "./inputField";
import { PopupElement } from "./popup"; import { PopupElement } from "./popup";
import RadioField from "./radioField"; import RadioField from "./radioField";
import Scrollable from "./scrollable"; import Scrollable from "./scrollable";
import { toast } from "./toast"; import { toast } from "./toast";
const InputField = (placeholder: string, label: string, name: string) => {
const div = document.createElement('div');
div.classList.add('input-field');
div.innerHTML = `
<input type="text" name="${name}" id="input-${name}" placeholder="${placeholder}" autocomplete="off" required="">
<label for="input-${name}">${label}</label>
`;
return div;
};
export default class PopupCreatePoll extends PopupElement { export default class PopupCreatePoll extends PopupElement {
private questionInput: HTMLInputElement; private questionInput: HTMLInputElement;
private questions: HTMLElement; private questions: HTMLElement;
@ -119,6 +108,19 @@ export default class PopupCreatePoll extends PopupElement {
this.scrollable = new Scrollable(this.body); this.scrollable = new Scrollable(this.body);
this.appendMoreField(); this.appendMoreField();
this.onEscape = () => {
return !this.getFilledAnswers().length;
};
}
private getFilledAnswers() {
const answers = Array.from(this.questions.children).map((el, idx) => {
const input = el.querySelector('input[type="text"]') as HTMLInputElement;
return input.value;
}).filter(v => !!v.trim());
return answers;
} }
onSubmitClick = (e: MouseEvent) => { onSubmitClick = (e: MouseEvent) => {
@ -134,10 +136,7 @@ export default class PopupCreatePoll extends PopupElement {
return; return;
} }
const answers = Array.from(this.questions.children).map((el, idx) => { const answers = this.getFilledAnswers();
const input = el.querySelector('input[type="text"]') as HTMLInputElement;
return input.value;
}).filter(v => !!v.trim());
if(answers.length < 2) { if(answers.length < 2) {
toast('Please enter at least two options.'); toast('Please enter at least two options.');

285
src/components/popupNewMedia.ts

@ -0,0 +1,285 @@
import { isTouchSupported } from "../helpers/touchSupport";
import appImManager from "../lib/appManagers/appImManager";
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import { calcImageInBox } from "../lib/utils";
import { Layouter, RectPart } from "./groupedLayout";
import InputField from "./inputField";
import { PopupElement } from "./popup";
import { ripple } from "./ripple";
import { wrapDocument } from "./wrappers";
type SendFileParams = Partial<{
file: File,
objectURL: string,
width: number,
height: number,
duration: number
}>;
export default class PopupNewMedia extends PopupElement {
private btnSend: HTMLElement;
private input: HTMLInputElement;
private mediaContainer: HTMLElement;
private willAttach: Partial<{
type: 'media' | 'document',
isMedia: boolean,
sendFileDetails: SendFileParams[]
}> = {
sendFileDetails: []
};
constructor(files: File[], willAttachType: PopupNewMedia['willAttach']['type']) {
super('popup-send-photo popup-new-media', null, {closable: true});
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, {once: true});
this.header.append(this.btnSend);
this.mediaContainer = document.createElement('div');
this.mediaContainer.classList.add('popup-photo');
const inputField = InputField('Add a caption...', 'Caption', 'photo-caption');
this.input = inputField.firstElementChild as HTMLInputElement;
this.container.append(this.mediaContainer, inputField);
this.attachFiles(files);
}
private onKeyDown = (e: KeyboardEvent) => {
const target = e.target as HTMLElement;
if(target.tagName != 'INPUT') {
this.input.focus();
}
if(e.key == 'Enter' && !isTouchSupported) {
this.btnSend.click();
}
};
public send = () => {
this.destroy();
let caption = this.input.value;
const willAttach = this.willAttach;
willAttach.isMedia = willAttach.type == 'media';
//console.log('will send files with options:', willAttach);
const peerID = appImManager.peerID;
const chatInputC = appImManager.chatInputC;
if(willAttach.sendFileDetails.length > 1 && willAttach.isMedia) {
appMessagesManager.sendAlbum(peerID, willAttach.sendFileDetails.map(d => d.file), Object.assign({
caption,
replyToMsgID: chatInputC.replyToMsgID
}, willAttach));
} else {
if(caption) {
if(willAttach.sendFileDetails.length > 1) {
appMessagesManager.sendText(peerID, caption, {replyToMsgID: chatInputC.replyToMsgID});
caption = '';
chatInputC.replyToMsgID = 0;
}
}
const promises = willAttach.sendFileDetails.map(params => {
const promise = appMessagesManager.sendFile(peerID, params.file, Object.assign({
//isMedia: willAttach.isMedia,
isMedia: params.file.type.includes('audio/') || willAttach.isMedia,
caption,
replyToMsgID: chatInputC.replyToMsgID
}, params));
caption = '';
chatInputC.replyToMsgID = 0;
return promise;
});
}
//Promise.all(promises);
//appMessagesManager.sendFile(appImManager.peerID, willAttach.file, willAttach);
chatInputC.onMessageSent();
};
public attachFile = (file: File) => {
const willAttach = this.willAttach;
return new Promise<HTMLDivElement>((resolve) => {
const params: SendFileParams = {};
params.file = file;
//console.log('selected file:', file, typeof(file), willAttach);
const itemDiv = document.createElement('div');
switch(willAttach.type) {
case 'media': {
const isVideo = file.type.indexOf('video/') === 0;
itemDiv.classList.add('popup-item-media');
if(isVideo) {
const video = document.createElement('video');
const source = document.createElement('source');
source.src = params.objectURL = URL.createObjectURL(file);
video.autoplay = false;
video.controls = false;
video.muted = true;
video.setAttribute('playsinline', '');
video.onloadeddata = () => {
params.width = video.videoWidth;
params.height = video.videoHeight;
params.duration = Math.floor(video.duration);
itemDiv.append(video);
resolve(itemDiv);
};
video.append(source);
} else {
const img = new Image();
img.src = params.objectURL = URL.createObjectURL(file);
img.onload = () => {
params.width = img.naturalWidth;
params.height = img.naturalHeight;
itemDiv.append(img);
resolve(itemDiv);
};
}
break;
}
case 'document': {
const isPhoto = file.type.indexOf('image/') !== -1;
if(isPhoto) {
params.objectURL = URL.createObjectURL(file);
}
const docDiv = wrapDocument({
file: file,
file_name: file.name || '',
size: file.size,
type: isPhoto ? 'photo' : 'doc',
url: params.objectURL
} as any, false, true);
const finish = () => {
itemDiv.append(docDiv);
resolve(itemDiv);
};
if(isPhoto) {
const img = new Image();
img.src = params.objectURL;
img.onload = () => {
params.width = img.naturalWidth;
params.height = img.naturalHeight;
finish();
};
img.onerror = finish;
} else {
finish();
}
break;
}
}
willAttach.sendFileDetails.push(params);
});
};
public attachFiles(files: File[]) {
const container = this.container;
const willAttach = this.willAttach;
files = files.filter(file => {
if(willAttach.type == 'media') {
return ['image/', 'video/'].find(s => file.type.indexOf(s) === 0);
} else {
return true;
}
});
if(files.length) {
if(willAttach.type == 'document') {
this.title.innerText = 'Send ' + (files.length > 1 ? files.length + ' Files' : 'File');
container.classList.add('is-document');
} else {
container.classList.add('is-media');
let foundPhotos = 0;
let foundVideos = 0;
files.forEach(file => {
if(file.type.indexOf('image/') === 0) ++foundPhotos;
else if(file.type.indexOf('video/') === 0) ++foundVideos;
});
if(foundPhotos && foundVideos) {
this.title.innerText = 'Send Album';
} else if(foundPhotos) {
this.title.innerText = 'Send ' + (foundPhotos > 1 ? foundPhotos + ' Photos' : 'Photo');
} else if(foundVideos) {
this.title.innerText = 'Send ' + (foundVideos > 1 ? foundVideos + ' Videos' : 'Video');
}
}
}
Promise.all(files.map(this.attachFile)).then(results => {
if(willAttach.type == 'media') {
if(willAttach.sendFileDetails.length > 1) {
container.classList.add('is-album');
const layouter = new Layouter(willAttach.sendFileDetails.map(o => ({w: o.width, h: o.height})), 380, 100, 4);
const layout = layouter.layout();
for(const {geometry, sides} of layout) {
const div = results.shift();
div.style.width = geometry.width + 'px';
div.style.height = geometry.height + 'px';
div.style.top = geometry.y + 'px';
div.style.left = geometry.x + 'px';
if(sides & RectPart.Right) {
this.mediaContainer.style.width = geometry.width + geometry.x + 'px';
}
if(sides & RectPart.Bottom) {
this.mediaContainer.style.height = geometry.height + geometry.y + 'px';
}
this.mediaContainer.append(div);
}
//console.log('chatInput album layout:', layout);
} else {
const params = willAttach.sendFileDetails[0];
const div = results[0];
const {w, h} = calcImageInBox(params.width, params.height, 380, 320);
div.style.width = w + 'px';
div.style.height = h + 'px';
this.mediaContainer.append(div);
}
} else {
this.mediaContainer.append(...results);
}
// show now
document.body.addEventListener('keydown', this.onKeyDown);
this.onClose = () => {
document.body.removeEventListener('keydown', this.onKeyDown);
};
this.show();
});
}
}

14
src/components/sidebarLeft/tabs/editProfile.ts

@ -1,12 +1,12 @@
import { SliderTab } from "../../slider";
import popupAvatar from "../../popupAvatar";
import apiManager from "../../../lib/mtproto/mtprotoworker";
import appProfileManager from "../../../lib/appManagers/appProfileManager";
import appSidebarLeft from ".."; import appSidebarLeft from "..";
import Scrollable from "../../scrollable"; import { InputFile } from "../../../layer";
import appProfileManager from "../../../lib/appManagers/appProfileManager";
import appUsersManager from "../../../lib/appManagers/appUsersManager"; import appUsersManager from "../../../lib/appManagers/appUsersManager";
import apiManager from "../../../lib/mtproto/mtprotoworker";
import $rootScope from "../../../lib/rootScope"; import $rootScope from "../../../lib/rootScope";
import { InputFile } from "../../../layer"; import PopupAvatar from "../../popupAvatar";
import Scrollable from "../../scrollable";
import { SliderTab } from "../../slider";
// TODO: аватарка не поменяется в этой вкладке после изменения почему-то (если поставить в другом клиенте, и потом тут проверить, для этого ещё вышел в чатлист) // TODO: аватарка не поменяется в этой вкладке после изменения почему-то (если поставить в другом клиенте, и потом тут проверить, для этого ещё вышел в чатлист)
@ -36,7 +36,7 @@ export default class AppEditProfileTab implements SliderTab {
constructor() { constructor() {
this.container.querySelector('.avatar-edit').addEventListener('click', () => { this.container.querySelector('.avatar-edit').addEventListener('click', () => {
popupAvatar.open(this.canvas, (_upload) => { new PopupAvatar().open(this.canvas, (_upload) => {
this.uploadAvatar = _upload; this.uploadAvatar = _upload;
this.handleChange(); this.handleChange();
this.avatarElem.remove(); this.avatarElem.remove();

8
src/components/sidebarLeft/tabs/newChannel.ts

@ -1,7 +1,7 @@
import { SliderTab } from "../../slider";
import popupAvatar from "../../popupAvatar";
import appChatsManager from "../../../lib/appManagers/appChatsManager";
import appSidebarLeft, { AppSidebarLeft } from ".."; import appSidebarLeft, { AppSidebarLeft } from "..";
import appChatsManager from "../../../lib/appManagers/appChatsManager";
import PopupAvatar from "../../popupAvatar";
import { SliderTab } from "../../slider";
export default class AppNewChannelTab implements SliderTab { export default class AppNewChannelTab implements SliderTab {
private container = document.querySelector('.new-channel-container') as HTMLDivElement; private container = document.querySelector('.new-channel-container') as HTMLDivElement;
@ -14,7 +14,7 @@ export default class AppNewChannelTab implements SliderTab {
constructor() { constructor() {
this.container.querySelector('.avatar-edit').addEventListener('click', () => { this.container.querySelector('.avatar-edit').addEventListener('click', () => {
popupAvatar.open(this.canvas, (_upload) => { new PopupAvatar().open(this.canvas, (_upload) => {
this.uploadAvatar = _upload; this.uploadAvatar = _upload;
}); });
}); });

12
src/components/sidebarLeft/tabs/newGroup.ts

@ -1,11 +1,11 @@
import { SliderTab } from "../../slider";
import { SearchGroup } from "../../appSearch";
import popupAvatar from "../../popupAvatar";
import appChatsManager from "../../../lib/appManagers/appChatsManager";
import appSidebarLeft, { AppSidebarLeft } from ".."; import appSidebarLeft, { AppSidebarLeft } from "..";
import Scrollable from "../../scrollable"; import appChatsManager from "../../../lib/appManagers/appChatsManager";
import appDialogsManager from "../../../lib/appManagers/appDialogsManager"; import appDialogsManager from "../../../lib/appManagers/appDialogsManager";
import appUsersManager from "../../../lib/appManagers/appUsersManager"; import appUsersManager from "../../../lib/appManagers/appUsersManager";
import { SearchGroup } from "../../appSearch";
import PopupAvatar from "../../popupAvatar";
import Scrollable from "../../scrollable";
import { SliderTab } from "../../slider";
export default class AppNewGroupTab implements SliderTab { export default class AppNewGroupTab implements SliderTab {
private container = document.querySelector('.new-group-container') as HTMLDivElement; private container = document.querySelector('.new-group-container') as HTMLDivElement;
@ -19,7 +19,7 @@ export default class AppNewGroupTab implements SliderTab {
constructor() { constructor() {
this.container.querySelector('.avatar-edit').addEventListener('click', () => { this.container.querySelector('.avatar-edit').addEventListener('click', () => {
popupAvatar.open(this.canvas, (_upload) => { new PopupAvatar().open(this.canvas, (_upload) => {
this.uploadAvatar = _upload; this.uploadAvatar = _upload;
}); });
}); });

27
src/index.hbs

@ -93,7 +93,7 @@
<div class="container center-align"> <div class="container center-align">
<div class="scrollable scrollable-y"> <div class="scrollable scrollable-y">
<div class="auth-image"></div> <div class="auth-image"></div>
<h4 class="phone">Enter a password</h4> <h4 class="phone">Enter Your Password</h4>
<p class="subtitle">Your account is protected with<br>an additional password</p> <p class="subtitle">Your account is protected with<br>an additional password</p>
<div class="input-wrapper"> <div class="input-wrapper">
<div class="input-field"> <div class="input-field">
@ -131,17 +131,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="popup popup-avatar hide" id="popup-avatar" style="display: none;">
<div class="popup-container z-depth-1">
<div class="popup-header">
<span class="popup-close tgico-close"></span>
<h6>Drag to Reposition</h6>
</div>
<div class="crop"></div>
<button class="btn-primary rp btn-circle btn-crop btn-icon tgico-check z-depth-1"></button>
<input type="file" style="display: none;" />
</div>
</div>
<div class="whole page-chats" style="display: none;" id="page-chats"> <div class="whole page-chats" style="display: none;" id="page-chats">
<svg style="position: absolute; top: -10000px; left: -10000px;"> <svg style="position: absolute; top: -10000px; left: -10000px;">
<defs id="svg-defs"> <defs id="svg-defs">
@ -186,20 +175,6 @@
<div class="media-viewer-switcher media-viewer-switcher-right menu-next"><span class="tgico-down media-viewer-next-button"></span></div> <div class="media-viewer-switcher media-viewer-switcher-right menu-next"><span class="tgico-down media-viewer-next-button"></span></div>
{{!-- </div> --}} {{!-- </div> --}}
</div> </div>
<div class="popup popup-send-photo popup-new-media">
<div class="popup-container z-depth-1">
<div class="popup-header">
<span class="btn-icon popup-close tgico-close"></span>
<div class="popup-title">Send Photo</div>
<button class="btn-primary rp">SEND</button>
</div>
<div class="popup-photo"></div>
<div class="input-field">
<input type="text" name="photo-caption" id="photo-caption" placeholder="Add a caption..." autocomplete="off" required />
<label for="photo-caption">Caption</label>
</div>
</div>
</div>
<div id="main-columns" class="tabs-container"> <div id="main-columns" class="tabs-container">
<div class="chats-container sidebar sidebar-left main-column" id="column-left"> <div class="chats-container sidebar sidebar-left main-column" id="column-left">
<div class="sidebar-slider tabs-container"> <div class="sidebar-slider tabs-container">

79
src/lib/appManagers/appImManager.ts

@ -6,6 +6,7 @@ import BubbleGroups from '../../components/bubbleGroups';
import { ChatAudio } from '../../components/chat/audio'; import { ChatAudio } from '../../components/chat/audio';
import { ChatContextMenu } from '../../components/chat/contextMenu'; import { ChatContextMenu } from '../../components/chat/contextMenu';
import { ChatInput } from '../../components/chat/input'; import { ChatInput } from '../../components/chat/input';
import { MessageRender } from '../../components/chat/messageRender';
import PinnedContainer from '../../components/chat/pinnedContainer'; import PinnedContainer from '../../components/chat/pinnedContainer';
import ReplyContainer from '../../components/chat/replyContainer'; import ReplyContainer from '../../components/chat/replyContainer';
import { ChatSearch } from '../../components/chat/search'; import { ChatSearch } from '../../components/chat/search';
@ -31,7 +32,7 @@ import apiManager from '../mtproto/mtprotoworker';
import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config'; import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config';
import { RichTextProcessor } from "../richtextprocessor"; import { RichTextProcessor } from "../richtextprocessor";
import $rootScope from '../rootScope'; import $rootScope from '../rootScope';
import { cancelEvent, findUpClassName, findUpTag, formatNumber, getObjectKeysAndSort, langPack, numberWithCommas, placeCaretAtEnd, whichChild } from "../utils"; import { cancelEvent, findUpClassName, findUpTag, getObjectKeysAndSort, langPack, numberWithCommas, placeCaretAtEnd, whichChild } from "../utils";
import apiUpdatesManager from './apiUpdatesManager'; import apiUpdatesManager from './apiUpdatesManager';
import appChatsManager, { Channel, Chat } from "./appChatsManager"; import appChatsManager, { Channel, Chat } from "./appChatsManager";
import appDialogsManager from "./appDialogsManager"; import appDialogsManager from "./appDialogsManager";
@ -618,26 +619,14 @@ export class AppImManager {
}); });
let onKeyDown = (e: KeyboardEvent) => { let onKeyDown = (e: KeyboardEvent) => {
if($rootScope.overlayIsActive) return;
let target = e.target as HTMLElement; let target = e.target as HTMLElement;
//if(target.tagName == 'INPUT') return; //if(target.tagName == 'INPUT') return;
//this.log('onkeydown', e); //this.log('onkeydown', e);
if(this.chatInputC.attachMediaPopUp.container.classList.contains('active')) {
if(target.tagName != 'INPUT') {
this.chatInputC.attachMediaPopUp.captionInput.focus();
}
if(e.key == 'Enter' && !isTouchSupported) {
this.chatInputC.attachMediaPopUp.sendBtn.click();
} else if(e.key == 'Escape') {
this.chatInputC.attachMediaPopUp.container.classList.remove('active');
}
return;
}
if(e.key == 'Escape') { if(e.key == 'Escape') {
if(appMediaViewer.wholeDiv.classList.contains('active')) { if(appMediaViewer.wholeDiv.classList.contains('active')) {
appMediaViewer.buttons.close.click(); appMediaViewer.buttons.close.click();
@ -777,13 +766,14 @@ export class AppImManager {
} }
public getAlbumBubble(groupID: string) { public getAlbumBubble(groupID: string) {
let group = appMessagesManager.groupedMessagesStorage[groupID]; const group = appMessagesManager.groupedMessagesStorage[groupID];
for(let i in group) { for(const mid in group) {
let mid = +i; if(this.bubbles[mid]) {
if(this.bubbles[mid]) return { return {
bubble: this.bubbles[mid], bubble: this.bubbles[mid],
message: appMessagesManager.getMessage(mid) message: appMessagesManager.getMessage(+mid)
}; };
}
} }
return null; return null;
@ -1622,42 +1612,6 @@ export class AppImManager {
return bubble; return bubble;
} }
// time section
let date = new Date(message.date * 1000);
let time = ('0' + date.getHours()).slice(-2) + ':' + ('0' + date.getMinutes()).slice(-2);
if(message.views) {
bubble.classList.add('channel-post');
time = formatNumber(message.views, 1) + ' <i class="tgico-channelviews"></i> ' + time;
if(!message.savedFrom) {
let forward = document.createElement('div');
forward.classList.add('bubble-beside-button', 'forward');
forward.innerHTML = `
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 24 24">
<defs>
<path d="M13.55 3.24L13.64 3.25L13.73 3.27L13.81 3.29L13.9 3.32L13.98 3.35L14.06 3.39L14.14 3.43L14.22 3.48L14.29 3.53L14.36 3.59L14.43 3.64L22.23 10.85L22.36 10.99L22.48 11.15L22.57 11.31L22.64 11.48L22.69 11.66L22.72 11.85L22.73 12.04L22.71 12.22L22.67 12.41L22.61 12.59L22.53 12.76L22.42 12.93L22.29 13.09L22.23 13.15L14.43 20.36L14.28 20.48L14.12 20.58L13.95 20.66L13.77 20.72L13.58 20.76L13.4 20.77L13.22 20.76L13.03 20.73L12.85 20.68L12.68 20.61L12.52 20.52L12.36 20.4L12.22 20.27L12.16 20.2L12.1 20.13L12.05 20.05L12.01 19.98L11.96 19.9L11.93 19.82L11.89 19.73L11.87 19.65L11.84 19.56L11.83 19.47L11.81 19.39L11.81 19.3L11.8 19.2L11.8 16.42L11 16.49L10.23 16.58L9.51 16.71L8.82 16.88L8.18 17.09L7.57 17.33L7.01 17.6L6.48 17.91L5.99 18.26L5.55 18.64L5.14 19.05L4.77 19.51L4.43 19.99L4.29 20.23L4.21 20.35L4.11 20.47L4 20.57L3.88 20.65L3.75 20.72L3.62 20.78L3.48 20.82L3.33 20.84L3.19 20.84L3.04 20.83L2.9 20.79L2.75 20.74L2.62 20.68L2.53 20.62L2.45 20.56L2.38 20.5L2.31 20.43L2.25 20.36L2.2 20.28L2.15 20.19L2.11 20.11L2.07 20.02L2.04 19.92L2.02 19.83L2.01 19.73L2 19.63L2.04 17.99L2.19 16.46L2.46 15.05L2.85 13.75L3.35 12.58L3.97 11.53L4.7 10.6L5.55 9.8L6.51 9.12L7.59 8.56L8.77 8.13L10.07 7.83L11.48 7.65L11.8 7.63L11.8 4.8L11.91 4.56L12.02 4.35L12.14 4.16L12.25 3.98L12.37 3.82L12.48 3.68L12.61 3.56L12.73 3.46L12.85 3.38L12.98 3.31L13.11 3.27L13.24 3.24L13.37 3.23L13.46 3.23L13.55 3.24Z" id="b13RmHDQtl"></path>
</defs>
<use xlink:href="#b13RmHDQtl" opacity="1" fill="#fff" fill-opacity="1"></use>
</svg>`;
bubbleContainer.append(forward);
bubble.classList.add('with-beside-button');
}
}
if(message.edit_date) {
bubble.classList.add('is-edited');
time = '<i class="edited">edited</i> ' + time;
}
let timeSpan = document.createElement('span');
timeSpan.classList.add('time');
let timeInner = document.createElement('div');
timeInner.classList.add('inner', 'tgico');
timeInner.innerHTML = time;
let messageMessage: string, totalEntities: any[]; let messageMessage: string, totalEntities: any[];
if(message.grouped_id) { if(message.grouped_id) {
@ -1676,9 +1630,7 @@ export class AppImManager {
messageMessage = undefined; messageMessage = undefined;
totalEntities = undefined; totalEntities = undefined;
} }
} } else {
if(!messageMessage && !totalEntities) {
messageMessage = message.message; messageMessage = message.message;
totalEntities = message.totalEntities; totalEntities = message.totalEntities;
} }
@ -1725,8 +1677,7 @@ export class AppImManager {
messageDiv.innerHTML = richText; messageDiv.innerHTML = richText;
} }
timeSpan.appendChild(timeInner); const timeSpan = MessageRender.setTime(message, bubble, bubbleContainer, messageDiv);
messageDiv.append(timeSpan);
bubbleContainer.prepend(messageDiv); bubbleContainer.prepend(messageDiv);
//bubble.prepend(timeSpan, messageDiv); // that's bad //bubble.prepend(timeSpan, messageDiv); // that's bad

1
src/lib/rootScope.ts

@ -78,6 +78,7 @@ const $rootScope = {
document.removeEventListener(name, callback); document.removeEventListener(name, callback);
}, },
overlayIsActive: false,
selectedPeerID: 0, selectedPeerID: 0,
myID: 0, myID: 0,
idle: { idle: {

8
src/pages/pageSignUp.ts

@ -1,9 +1,9 @@
import {putPreloader} from '../components/misc'; import { putPreloader } from '../components/misc';
import pageIm from './pageIm'; import PopupAvatar from '../components/popupAvatar';
//import apiManager from '../lib/mtproto/apiManager'; //import apiManager from '../lib/mtproto/apiManager';
import apiManager from '../lib/mtproto/mtprotoworker'; import apiManager from '../lib/mtproto/mtprotoworker';
import Page from './page'; import Page from './page';
import popupAvatar from '../components/popupAvatar'; import pageIm from './pageIm';
let authCode: { let authCode: {
'phone_number': string, 'phone_number': string,
@ -17,7 +17,7 @@ let onFirstMount = () => import('../lib/appManagers/appProfileManager').then(imp
let uploadAvatar: () => Promise<any>; let uploadAvatar: () => Promise<any>;
pageElement.querySelector('.auth-image').addEventListener('click', () => { pageElement.querySelector('.auth-image').addEventListener('click', () => {
popupAvatar.open(avatarPreview, (_uploadAvatar) => { new PopupAvatar().open(avatarPreview, (_uploadAvatar) => {
uploadAvatar = _uploadAvatar; uploadAvatar = _uploadAvatar;
}); });
}); });

22
src/scss/partials/_chat.scss

@ -681,10 +681,7 @@
padding: 1rem; padding: 1rem;
&-subtitle { &-subtitle {
line-height: 13px !important; line-height: 1 !important;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
max-width: 280px; max-width: 280px;
} }
} }
@ -714,7 +711,7 @@
} }
&-border { &-border {
height: 32px; height: 2rem;
border-radius: 1px; border-radius: 1px;
min-width: 2px; min-width: 2px;
background: $color-blue; background: $color-blue;
@ -727,7 +724,7 @@
overflow: hidden; overflow: hidden;
pointer-events: none; pointer-events: none;
position: relative; position: relative;
height: 32px; height: 2rem;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-between; justify-content: space-between;
@ -752,14 +749,13 @@
} }
&-subtitle { &-subtitle {
white-space: nowrap;
color: #111; color: #111;
} }
&-media { &-media {
height: 32px; height: 2rem;
width: 32px; width: 2rem;
border-radius: 8px; border-radius: .5rem;
overflow: hidden; overflow: hidden;
position: absolute; position: absolute;
left: 0; left: 0;
@ -775,13 +771,13 @@
} }
img.emoji { img.emoji {
height: 16px; height: 1rem;
width: 16px; width: 1rem;
vertical-align: top; vertical-align: top;
} }
span.emoji { span.emoji {
font-size: 16px; font-size: 1rem;
vertical-align: unset; vertical-align: unset;
} }
} }

Loading…
Cancel
Save