Browse Source

context menu & delete messages & pin message

master
Eduard Kuzmenko 5 years ago
parent
commit
fd6a39c2bc
  1. 88
      src/components/misc.ts
  2. 32
      src/components/pageIm.ts
  3. 22
      src/components/pageSignUp.ts
  4. 174
      src/lib/appManagers/appImManager.ts
  5. 1
      src/lib/appManagers/appMessagesManager.ts
  6. 4
      src/lib/lottieLoader.ts
  7. 11
      src/lib/utils.js
  8. 43
      src/scss/partials/_chat.scss
  9. 7
      src/scss/partials/_chatlist.scss
  10. 22
      src/scss/style.scss

88
src/components/misc.ts

@ -410,7 +410,7 @@ export function scrollable(el: HTMLDivElement, x = false, y = true) { @@ -410,7 +410,7 @@ export function scrollable(el: HTMLDivElement, x = false, y = true) {
let thumbSize = 0;
window.addEventListener('resize', resize);
//container.addEventListener('DOMNodeInserted', resize);
let hiddenElements: {
up: Element[],
down: Element[]
@ -418,23 +418,23 @@ export function scrollable(el: HTMLDivElement, x = false, y = true) { @@ -418,23 +418,23 @@ export function scrollable(el: HTMLDivElement, x = false, y = true) {
up: [],
down: []
};
let paddings = {up: 0, down: 0};
let paddingTopDiv = document.createElement('div');
paddingTopDiv.classList.add('scroll-padding');
let paddingBottomDiv = document.createElement('div');
paddingBottomDiv.classList.add('scroll-padding');
let onScroll = (e: Event) => {
// @ts-ignore
//let st = container[scrollSide];
// @ts-ignore
if(container[scrollType] != scrollSize || thumbSize == 0) {
resize();
}
//let splitUp = container.querySelector('ul');
let splitUp = container.children[1];
let children = Array.from(splitUp.children) as HTMLElement[];
@ -447,57 +447,57 @@ export function scrollable(el: HTMLDivElement, x = false, y = true) { @@ -447,57 +447,57 @@ export function scrollable(el: HTMLDivElement, x = false, y = true) {
lastVisible = i;
}
}
if(firstVisible > 0) {
let sliced = children.slice(0, firstVisible);
for(let child of sliced) {
paddings.up += child.scrollHeight;
hiddenElements.up.push(child);
child.parentElement.removeChild(child);
}
//console.log('sliced up', sliced.length);
//sliced.forEach(child => child.style.display = 'none');
paddingTopDiv.style.height = paddings.up + 'px';
//console.log('onscroll need to add padding: ', paddings.up);
} else if(hiddenElements.up.length) {
while(isElementInViewport(paddingTopDiv) && paddings.up) {
let child = hiddenElements.up.pop();
splitUp.prepend(child);
paddings.up -= child.scrollHeight;
paddingTopDiv.style.height = paddings.up + 'px';
}
}
if(lastVisible < (length - 1)) {
let sliced = children.slice(lastVisible + 1).reverse();
for(let child of sliced) {
paddings.down += child.scrollHeight;
hiddenElements.down.unshift(child);
child.parentElement.removeChild(child);
}
//console.log('onscroll sliced down', sliced.length);
//sliced.forEach(child => child.style.display = 'none');
paddingBottomDiv.style.height = paddings.down + 'px';
//console.log('onscroll need to add padding: ', paddings.up);
} else if(hiddenElements.down.length) {
while(isElementInViewport(paddingBottomDiv) && paddings.down) {
let child = hiddenElements.down.shift();
splitUp.append(child);
paddings.down -= child.scrollHeight;
paddingBottomDiv.style.height = paddings.down + 'px';
}
}
//console.log('onscroll', container, firstVisible, lastVisible, hiddenElements);
// @ts-ignore
@ -508,7 +508,7 @@ export function scrollable(el: HTMLDivElement, x = false, y = true) { @@ -508,7 +508,7 @@ export function scrollable(el: HTMLDivElement, x = false, y = true) {
// @ts-ignore
thumb.style[side] = (value >= maxValue ? maxValue : value) + '%';
//lastScrollPos = st;
};
@ -758,15 +758,15 @@ export function getNearestDc() { @@ -758,15 +758,15 @@ export function getNearestDc() {
export function formatPhoneNumber(str: string) {
str = str.replace(/\D/g, '');
let phoneCode = str.slice(0, 6);
console.log('str', str, phoneCode);
let sortedCountries = Config.Countries.slice().sort((a, b) => b.phoneCode.length - a.phoneCode.length);
let country = sortedCountries.find((c) => {
return c.phoneCode.split(' and ').find((c) => phoneCode.indexOf(c.replace(/\D/g, '')) == 0);
});
let pattern = country ? country.pattern || country.phoneCode : '';
if(country) {
pattern.split('').forEach((symbol, idx) => {
@ -779,6 +779,44 @@ export function formatPhoneNumber(str: string) { @@ -779,6 +779,44 @@ export function formatPhoneNumber(str: string) {
str = str.slice(0, country.pattern.length);
}
}
return {formatted: str, country};
}
let onMouseMove = (e: MouseEvent) => {
let rect = openedMenu.getBoundingClientRect();
let {clientX, clientY} = e;
let diffX = clientX >= rect.right ? clientX - rect.right : rect.left - clientX;
let diffY = clientY >= rect.bottom ? clientY - rect.bottom : rect.top - clientY;
if(diffX >= 100 || diffY >= 100) {
openedMenu.classList.remove('active');
openedMenu.parentElement.classList.remove('menu-open');
//openedMenu.parentElement.click();
}
//console.log('mousemove', diffX, diffY);
};
let openedMenu: HTMLDivElement = null;
export function openBtnMenu(menuElement: HTMLDivElement) {
if(openedMenu) {
openedMenu.classList.remove('active');
openedMenu.parentElement.classList.remove('menu-open');
}
openedMenu = menuElement;
openedMenu.classList.add('active');
window.addEventListener('click', () => {
if(openedMenu) {
openedMenu.parentElement.classList.remove('menu-open');
openedMenu.classList.remove('active');
openedMenu = null;
}
window.removeEventListener('mousemove', onMouseMove);
}, {once: true});
window.addEventListener('mousemove', onMouseMove);
}

32
src/components/pageIm.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
//import { appImManager, appMessagesManager, appDialogsManager, apiUpdatesManager, appUsersManager } from "../lib/services";
import { horizontalMenu, wrapSticker, MTDocument, LazyLoadQueue } from "./misc";
import { horizontalMenu, wrapSticker, MTDocument, LazyLoadQueue, openBtnMenu } from "./misc";
import Scrollable from './scrollable';
import { whichChild, findUpTag } from "../lib/utils";
@ -712,27 +712,13 @@ export default () => import('../lib/services').then(services => { @@ -712,27 +712,13 @@ export default () => import('../lib/services').then(services => {
toggleEmoticons.classList.toggle('active');
}; */
let openedMenu: HTMLDivElement = null;
let onMouseMove = (e: MouseEvent) => {
let rect = openedMenu.getBoundingClientRect();
let {clientX, clientY} = e;
let diffX = clientX >= rect.right ? clientX - rect.right : rect.left - clientX;
let diffY = clientY >= rect.bottom ? clientY - rect.bottom : rect.top - clientY;
if(diffX >= 100 || diffY >= 100) {
openedMenu.parentElement.click();
}
//console.log('mousemove', diffX, diffY);
};
Array.from(document.getElementsByClassName('btn-menu-toggle')).forEach((el) => {
el.addEventListener('click', (e) => {
console.log('click pageIm');
if(!el.classList.contains('btn-menu-toggle')) return false;
window.removeEventListener('mousemove', onMouseMove);
openedMenu = el.querySelector('.btn-menu');
//window.removeEventListener('mousemove', onMouseMove);
let openedMenu = el.querySelector('.btn-menu') as HTMLDivElement;
e.cancelBubble = true;
if(el.classList.contains('menu-open')) {
@ -740,16 +726,8 @@ export default () => import('../lib/services').then(services => { @@ -740,16 +726,8 @@ export default () => import('../lib/services').then(services => {
openedMenu.classList.remove('active');
} else {
el.classList.add('menu-open');
openedMenu.classList.add('active');
window.addEventListener('click', () => {
//(el as HTMLDivElement).click();
el.classList.remove('menu-open');
openedMenu.classList.remove('active');
window.removeEventListener('mousemove', onMouseMove);
}, {once: true});
window.addEventListener('mousemove', onMouseMove);
openBtnMenu(openedMenu);
}
});
});

22
src/components/pageSignUp.ts

@ -18,22 +18,6 @@ export default (_authCode: typeof authCode) => { @@ -18,22 +18,6 @@ export default (_authCode: typeof authCode) => {
let pageElement = document.body.getElementsByClassName('page-signUp')[0] as HTMLDivElement;
pageElement.style.display = '';
let findUpClassName = (el: Element | HTMLElement, className: string): HTMLElement => {
while(el.parentNode) {
// @ts-ignore
el = el.parentNode;
if(el.classList.contains(className))
return <HTMLElement>el;
}
return null;
};
Array.from(document.body.getElementsByClassName('popup-close')).forEach(el => {
let popup = findUpClassName(el, 'popup');
el.addEventListener('click', () => {
popup.classList.remove('is-visible');
});
});
const avatarInput = document.getElementById('avatar-input') as HTMLInputElement;
const avatarPopup = pageElement.getElementsByClassName('popup-avatar')[0];
const avatarPreview = pageElement.querySelector('#canvas-avatar') as HTMLCanvasElement;
@ -46,7 +30,7 @@ export default (_authCode: typeof authCode) => { @@ -46,7 +30,7 @@ export default (_authCode: typeof authCode) => {
(avatarPopup.getElementsByClassName('popup-close')[0] as HTMLButtonElement)
.addEventListener('click', function(this, e) {
/* let popup = findUpClassName(this, 'popup');
popup.classList.remove('is-visible'); */
popup.classList.remove('active'); */
setTimeout(() => {
cropper.removeHandlers();
@ -67,7 +51,7 @@ export default (_authCode: typeof authCode) => { @@ -67,7 +51,7 @@ export default (_authCode: typeof authCode) => {
// apply
avatarPopup.getElementsByClassName('btn-crop')[0].addEventListener('click', () => {
cropper.crop();
avatarPopup.classList.remove('is-visible');
avatarPopup.classList.remove('active');
cropper.removeHandlers();
avatarPreview.toBlob(blob => {
@ -120,7 +104,7 @@ export default (_authCode: typeof authCode) => { @@ -120,7 +104,7 @@ export default (_authCode: typeof authCode) => {
avatarInput.value = '';
};
avatarPopup.classList.add('is-visible');
avatarPopup.classList.add('active');
//console.log(contents);
};

174
src/lib/appManagers/appImManager.ts

@ -1,10 +1,10 @@ @@ -1,10 +1,10 @@
import apiManager from '../mtproto/apiManager';
import { $rootScope, isElementInViewport, numberWithCommas } from "../utils";
import { $rootScope, isElementInViewport, numberWithCommas, findUpClassName } from "../utils";
import appUsersManager from "./appUsersManager";
import appMessagesManager from "./appMessagesManager";
import appPeersManager from "./appPeersManager";
import appProfileManager from "./appProfileManager";
import { ProgressivePreloader, wrapDocument, wrapSticker, wrapVideo, wrapPhoto } from "../../components/misc";
import { ProgressivePreloader, wrapDocument, wrapSticker, wrapVideo, wrapPhoto, openBtnMenu } from "../../components/misc";
import appDialogsManager from "./appDialogsManager";
import { RichTextProcessor } from "../richtextprocessor";
import appPhotosManager from "./appPhotosManager";
@ -16,6 +16,7 @@ import appMediaViewer from "./appMediaViewer"; @@ -16,6 +16,7 @@ import appMediaViewer from "./appMediaViewer";
import appSidebarLeft from "./appSidebarLeft";
import appChatsManager from "./appChatsManager";
import appMessagesIDsManager from "./appMessagesIDsManager";
import apiUpdatesManager from './apiUpdatesManager';
console.log('appImManager included!');
@ -133,13 +134,29 @@ export class AppImManager { @@ -133,13 +134,29 @@ export class AppImManager {
private topbar: HTMLDivElement = null;
private chatInput: HTMLDivElement = null;
scrolledAll: boolean;
private scrolledAll: boolean;
public contextMenu = document.getElementById('bubble-contextmenu') as HTMLDivElement;
private contextMenuPin = this.contextMenu.querySelector('.menu-pin') as HTMLDivElement;
private contextMenuMsgID: number;
private popupDeleteMessage: {
popupEl?: HTMLDivElement,
deleteBothBtn?: HTMLButtonElement,
deleteMeBtn?: HTMLButtonElement,
cancelBtn?: HTMLButtonElement
} = {};
constructor() {
this.log = logger('IM');
this.preloader = new ProgressivePreloader(null, false);
this.popupDeleteMessage.popupEl = this.pageEl.querySelector('.popup-delete-message') as HTMLDivElement;
this.popupDeleteMessage.deleteBothBtn = this.popupDeleteMessage.popupEl.querySelector('.popup-delete-both') as HTMLButtonElement;
this.popupDeleteMessage.deleteMeBtn = this.popupDeleteMessage.popupEl.querySelector('.popup-delete-me') as HTMLButtonElement;
this.popupDeleteMessage.cancelBtn = this.popupDeleteMessage.popupEl.querySelector('.popup-close') as HTMLButtonElement;
apiManager.getUserID().then((id) => {
this.myID = id;
});
@ -256,6 +273,111 @@ export class AppImManager { @@ -256,6 +273,111 @@ export class AppImManager {
this.btnMenuMute.addEventListener('click', () => this.mutePeer());
this.btnMute.addEventListener('click', () => this.mutePeer());
this.chatInner.addEventListener('contextmenu', e => {
let bubble = findUpClassName(e.target, 'bubble');
if(bubble) {
e.preventDefault();
e.cancelBubble = true;
let msgID = 0;
for(let id in this.bubbles) {
if(this.bubbles[id] === bubble) {
msgID = +id;
break;
}
}
if(!msgID) return;
if(this.myID == this.peerID ||
(this.peerID < 0 && !appPeersManager.isChannel(this.peerID) && !appPeersManager.isMegagroup(this.peerID))) {
this.contextMenuPin.style.display = '';
} else this.contextMenuPin.style.display = 'none';
this.contextMenuMsgID = msgID;
let side = bubble.parentElement.classList.contains('in') ? 'left' : 'right';
this.contextMenu.classList.remove('bottom-left', 'bottom-right');
this.contextMenu.classList.add(side == 'left' ? 'bottom-right' : 'bottom-left');
let {clientX, clientY} = e;
this.contextMenu.style.left = (side == 'right' ? clientX - this.contextMenu.scrollWidth : clientX) + 'px';
if((clientY + this.contextMenu.scrollHeight) > window.innerHeight) {
this.contextMenu.style.top = (window.innerHeight - this.contextMenu.scrollHeight) + 'px';
} else {
this.contextMenu.style.top = clientY + 'px';
}
//this.contextMenu.classList.add('active');
openBtnMenu(this.contextMenu);
this.log('contextmenu', e, bubble, msgID, side);
}
});
this.contextMenu.querySelector('.menu-copy').addEventListener('click', () => {
let message = appMessagesManager.getMessage(this.contextMenuMsgID);
let str = message ? message.message : '';
var textArea = document.createElement("textarea");
textArea.value = str;
textArea.style.position = "fixed"; //avoid scrolling to bottom
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
try {
document.execCommand('copy');
} catch (err) {
console.error('Oops, unable to copy', err);
}
document.body.removeChild(textArea);
});
this.contextMenu.querySelector('.menu-delete').addEventListener('click', () => {
if(this.peerID == this.myID) {
this.popupDeleteMessage.deleteBothBtn.style.display = 'none';
this.popupDeleteMessage.deleteMeBtn.innerText = 'DELETE';
} else {
this.popupDeleteMessage.deleteBothBtn.style.display = '';
this.popupDeleteMessage.deleteMeBtn.innerText = 'DELETE JUST FOR ME';
if(this.peerID > 0) {
let title = appPeersManager.getPeerTitle(this.peerID);
this.popupDeleteMessage.deleteBothBtn.innerText = 'DELETE FOR ME AND ' + title.split(' ')[0];
} else {
this.popupDeleteMessage.deleteBothBtn.innerText = 'DELETE FOR ALL';
}
}
this.popupDeleteMessage.popupEl.classList.add('active');
});
this.contextMenuPin.addEventListener('click', () => {
apiManager.invokeApi('messages.updatePinnedMessage', {
flags: 0,
peer: appPeersManager.getInputPeerByID(this.peerID),
id: this.contextMenuMsgID
}).then(updates => {
this.log('pinned updates:', updates);
apiUpdatesManager.processUpdateMessage(updates);
});
});
this.popupDeleteMessage.deleteBothBtn.addEventListener('click', () => {
this.deleteMessages(true);
this.popupDeleteMessage.cancelBtn.click();
});
this.popupDeleteMessage.deleteMeBtn.addEventListener('click', () => {
this.deleteMessages(false);
this.popupDeleteMessage.cancelBtn.click();
});
this.updateStatusInterval = window.setInterval(() => this.updateStatus(), 50e3);
this.updateStatus();
setInterval(() => this.setPeerStatus(), 60e3);
@ -263,6 +385,36 @@ export class AppImManager { @@ -263,6 +385,36 @@ export class AppImManager {
this.loadMediaQueueProcess();
}
public deleteMessages(revoke = false) {
let flags = revoke ? 1 : 0;
let ids = [this.contextMenuMsgID];
apiManager.invokeApi('messages.deleteMessages', {
flags: flags,
revoke: revoke,
id: ids
}).then((affectedMessages: any) => {
this.log('deleted messages:', affectedMessages);
apiUpdatesManager.processUpdateMessage({
_: 'updateShort',
update: {
_: 'updatePts',
pts: affectedMessages.pts,
pts_count: affectedMessages.pts_count
}
});
apiUpdatesManager.processUpdateMessage({
_: 'updateShort',
update: {
_: 'updateDeleteMessages',
messages: ids
}
});
});
}
public loadMediaQueuePush(cb: () => Promise<void>) {
this.loadMediaQueue.push(cb);
this.loadMediaQueueProcess();
@ -1344,6 +1496,22 @@ export class AppImManager { @@ -1344,6 +1496,22 @@ export class AppImManager {
this.log('updateNotifySettings', peerID, notify_settings);
break;
}
case 'updateChatPinnedMessage':
case 'updateUserPinnedMessage': {
let {id} = update;
this.log('updateUserPinnedMessage', update);
this.pinnedMsgID = id;
// hz nado li tut appMessagesIDsManager.getFullMessageID(update.max_id, channelID);
let peerID = update.user_id || -update.chat_id || -update.channel_id;
if(peerID == this.peerID) {
appMessagesManager.wrapSingleMessage(id);
}
break;
}
}
}
}

1
src/lib/appManagers/appMessagesManager.ts

@ -7,7 +7,6 @@ import { nextRandomInt, bigint } from "../bin_utils"; @@ -7,7 +7,6 @@ import { nextRandomInt, bigint } from "../bin_utils";
import { MTProto, telegramMeWebService } from "../mtproto/mtproto";
import apiUpdatesManager from "./apiUpdatesManager";
import appPhotosManager from "./appPhotosManager";
import appProfileManager from "./appProfileManager";
import AppStorage from '../storage';
import AppPeersManager from "./appPeersManager";

4
src/lib/lottieLoader.ts

@ -33,7 +33,7 @@ class LottieLoader { @@ -33,7 +33,7 @@ class LottieLoader {
if(canvas && isElementInViewport(container)) {
let c = container.firstElementChild as HTMLCanvasElement;
if(!c.height && !c.width) {
console.log('lottie need resize');
//console.log('lottie need resize');
animation.resize();
}
}
@ -104,7 +104,7 @@ class LottieLoader { @@ -104,7 +104,7 @@ class LottieLoader {
public getAnimation(el: HTMLElement, group = '') {
let groups = group ? [group] : Object.keys(this.animations);
console.log('getAnimation', groups, this.animations);
//console.log('getAnimation', groups, this.animations);
for(let group of groups) {
let animations = this.animations[group];

11
src/lib/utils.js

@ -324,6 +324,17 @@ export function numberWithCommas(x) { @@ -324,6 +324,17 @@ export function numberWithCommas(x) {
return parts.join(".");
}
export function findUpClassName(el, className) {
if(el.classList.contains(className)) return el; // 03.02.2020
while(el.parentNode) {
el = el.parentNode;
if(el.classList.contains(className))
return el;
}
return null;
}
export function findUpTag(el, tag) {
if(el.tagName == tag) return el; // 03.02.2020

43
src/scss/partials/_chat.scss

@ -883,6 +883,12 @@ @@ -883,6 +883,12 @@
} */
}
#bubble-contextmenu {
position: fixed;
right: auto;
bottom: auto;
}
.emoji-dropdown {
position: absolute;
left: 0;
@ -1088,3 +1094,40 @@ @@ -1088,3 +1094,40 @@
}
}
}
.popup {
&.popup-delete-message {
.popup-header {
margin-bottom: 1rem;
}
}
.popup-buttons {
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-end;
button {
background: none;
outline: none;
border: none;
padding: .5rem .5rem;
text-transform: uppercase;
transition: .2s;
border-radius: $border-radius;
cursor: pointer;
color: $blue;
&:hover {
background-color: rgba(112, 117, 121, 0.08);
}
& + button {
margin-top: .5rem;
}
}
}
}

7
src/scss/partials/_chatlist.scss

@ -64,7 +64,7 @@ @@ -64,7 +64,7 @@
position: relative;
cursor: pointer;
padding: 0 8.5px;
margin: 0 8.5px;
margin: 0 8.5px 0 8px;
overflow: hidden;
&:hover {
@ -146,11 +146,12 @@ @@ -146,11 +146,12 @@
}
.message-status {
margin-right: .25rem;
margin-right: .1rem;
margin-top: .3rem;
&[class*=" tgico-"] {
color: $success-color;
font-size: 1.15rem;
font-size: 1.25rem;
}
}

22
src/scss/style.scss

@ -175,6 +175,20 @@ input { @@ -175,6 +175,20 @@ input {
transform-origin: top left;
}
&.top-left {
top: initial;
right: 0;
bottom: 100%;
transform-origin: bottom right;
}
&.top-right {
top: initial;
left: 0;
bottom: 100%;
transform-origin: bottom left;
}
> div {
display: flex;
position: relative;
@ -925,7 +939,7 @@ $width: 100px; @@ -925,7 +939,7 @@ $width: 100px;
}
.popup {
position: fixed;
position: fixed!important;
left: 0;
top: 0;
height: 100%;
@ -948,7 +962,7 @@ $width: 100px; @@ -948,7 +962,7 @@ $width: 100px;
justify-content: center;
}
.popup.is-visible {
.popup.active {
opacity: 1;
visibility: visible;
-webkit-transition: opacity 0.3s 0s, visibility 0s 0s;
@ -977,7 +991,7 @@ $width: 100px; @@ -977,7 +991,7 @@ $width: 100px;
transition-duration: 0.3s;
}
.popup-close {
span.popup-close {
/* position: absolute;
left: 20px;
top: 12.5px; */
@ -1021,7 +1035,7 @@ $width: 100px; @@ -1021,7 +1035,7 @@ $width: 100px;
} */
}
.popup.is-visible .popup-container {
.popup.active .popup-container {
-webkit-transform: translateY(0);
-moz-transform: translateY(0);
-ms-transform: translateY(0);

Loading…
Cancel
Save