Browse Source

Fix context menu again

Multiselect fixes for desktop & mobile Safari
Fix multiselect album on mobiles
master
Eduard Kuzmenko 4 years ago
parent
commit
984b04ab40
  1. 6
      src/components/appMediaViewer.ts
  2. 24
      src/components/audio.ts
  3. 5
      src/components/buttonMenu.ts
  4. 6
      src/components/buttonMenuToggle.ts
  5. 41
      src/components/chat/contextMenu.ts
  6. 4
      src/components/chat/selection.ts
  7. 38
      src/components/misc.ts
  8. 18
      src/components/poll.ts
  9. 3
      src/components/popup.ts
  10. 14
      src/components/sidebarLeft/index.ts
  11. 8
      src/components/wrappers.ts
  12. 52
      src/helpers/dom.ts
  13. 2
      src/lib/appManagers/apiUpdatesManager.ts
  14. 60
      src/lib/appManagers/appDialogsManager.ts
  15. 29
      src/lib/appManagers/appImManager.ts
  16. 2
      src/scss/components/_global.scss
  17. 1
      src/scss/partials/_avatar.scss
  18. 13
      src/scss/partials/_button.scss
  19. 13
      src/scss/partials/_chatBubble.scss
  20. 2
      src/scss/partials/_input.scss
  21. 15
      src/scss/partials/_leftSidebar.scss
  22. 35
      src/scss/style.scss

6
src/components/appMediaViewer.ts

@ -1173,7 +1173,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet @@ -1173,7 +1173,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
this.openMedia(appMessagesManager.getMessage(target.mid), target.element);
};
onForwardClick = (e: MouseEvent) => {
onForwardClick = () => {
if(this.currentMessageID) {
//appSidebarRight.forwardTab.open([this.currentMessageID]);
new PopupForward([this.currentMessageID], () => {
@ -1198,7 +1198,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet @@ -1198,7 +1198,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
}
};
onDownloadClick = (e: MouseEvent) => {
onDownloadClick = () => {
const message = appMessagesManager.getMessage(this.currentMessageID);
if(message.media.photo) {
appPhotosManager.savePhotoFile(message.media.photo);
@ -1370,7 +1370,7 @@ export class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete', AppMe @@ -1370,7 +1370,7 @@ export class AppMediaViewerAvatar extends AppMediaViewerBase<'', 'delete', AppMe
this.openMedia(target.photoID, target.element, 1);
};
onDownloadClick = (e: MouseEvent) => {
onDownloadClick = () => {
appPhotosManager.savePhotoFile(appPhotosManager.getPhoto(this.currentPhotoID));
};

24
src/components/audio.ts

@ -11,6 +11,7 @@ import { isSafari } from "../helpers/userAgent"; @@ -11,6 +11,7 @@ import { isSafari } from "../helpers/userAgent";
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import rootScope from "../lib/rootScope";
import './middleEllipsis';
import { cancelEvent, CLICK_EVENT_NAME } from "../helpers/dom";
rootScope.on('messages_media_read', e => {
const mids = e.detail;
@ -213,12 +214,14 @@ function wrapVoiceMessage(doc: MyDocument, audioEl: AudioElement, mid: number) { @@ -213,12 +214,14 @@ function wrapVoiceMessage(doc: MyDocument, audioEl: AudioElement, mid: number) {
mousedown = false;
}
});
progress.addEventListener('click', (e) => {
progress.addEventListener(CLICK_EVENT_NAME, (e) => {
cancelEvent(e);
if(!audio.paused) scrub(e);
});
function scrub(e: MouseEvent) {
const scrubTime = e.offsetX / availW /* width */ * audio.duration;
function scrub(e: MouseEvent | TouchEvent) {
const offsetX = e instanceof MouseEvent ? e.offsetX : e.changedTouches[0].clientX;
const scrubTime = offsetX / availW /* width */ * audio.duration;
lastIndex = Math.round(scrubTime / audio.duration * barCount);
rects.slice(0, lastIndex + 1).forEach(node => node.classList.add('active'));
@ -366,7 +369,8 @@ export default class AudioElement extends HTMLElement { @@ -366,7 +369,8 @@ export default class AudioElement extends HTMLElement {
audioTimeDiv.innerText = String(audio.currentTime | 0).toHHMMSS(true) + ' / ' + durationStr;
}
toggle.addEventListener('click', () => {
toggle.addEventListener(CLICK_EVENT_NAME, (e) => {
cancelEvent(e);
if(audio.paused) audio.play().catch(() => {});
else audio.pause();
});
@ -395,7 +399,8 @@ export default class AudioElement extends HTMLElement { @@ -395,7 +399,8 @@ export default class AudioElement extends HTMLElement {
if(doc.type == 'voice') {
let download: Download;
const onClick = () => {
const onClick = (e: Event) => {
cancelEvent(e);
if(!download) {
if(!preloader) {
preloader = new ProgressivePreloader(null, true);
@ -406,7 +411,7 @@ export default class AudioElement extends HTMLElement { @@ -406,7 +411,7 @@ export default class AudioElement extends HTMLElement {
download.then(() => {
downloadDiv.remove();
this.removeEventListener('click', onClick);
this.removeEventListener(CLICK_EVENT_NAME, onClick);
onLoad();
}).catch(err => {
if(err.name === 'AbortError') {
@ -422,7 +427,7 @@ export default class AudioElement extends HTMLElement { @@ -422,7 +427,7 @@ export default class AudioElement extends HTMLElement {
}
};
this.addEventListener('click', onClick);
this.addEventListener(CLICK_EVENT_NAME, onClick);
this.click();
} else {
onLoad(false);
@ -430,8 +435,9 @@ export default class AudioElement extends HTMLElement { @@ -430,8 +435,9 @@ export default class AudioElement extends HTMLElement {
//if(appMediaPlaybackController.mediaExists(mid)) { // чтобы показать прогресс, если аудио уже было скачано
//onLoad();
//} else {
const r = () => {
const r = (e: Event) => {
//onLoad();
cancelEvent(e);
appMediaPlaybackController.resolveWaitingForLoadMedia(mid);
appMediaPlaybackController.willBePlayed(this.audio); // prepare for loading audio
@ -464,7 +470,7 @@ export default class AudioElement extends HTMLElement { @@ -464,7 +470,7 @@ export default class AudioElement extends HTMLElement {
});
};
this.addEventListener('click', r, {once: true});
this.addEventListener(CLICK_EVENT_NAME, r, {once: true});
//}
}
} else {

5
src/components/buttonMenu.ts

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
import { CLICK_EVENT_NAME } from "../helpers/dom";
import { ripple } from "./ripple";
export type ButtonMenuItemOptions = {icon: string, text: string, onClick: (e: MouseEvent) => void, element?: HTMLElement};
export type ButtonMenuItemOptions = {icon: string, text: string, onClick: (e: MouseEvent | TouchEvent) => void, element?: HTMLElement};
const ButtonMenuItem = (options: ButtonMenuItemOptions) => {
if(options.element) return options.element;
@ -11,7 +12,7 @@ const ButtonMenuItem = (options: ButtonMenuItemOptions) => { @@ -11,7 +12,7 @@ const ButtonMenuItem = (options: ButtonMenuItemOptions) => {
el.innerText = text;
ripple(el);
el.addEventListener('click', onClick);
el.addEventListener(CLICK_EVENT_NAME, onClick);
return options.element = el;
};

6
src/components/buttonMenuToggle.ts

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
import { cancelEvent, CLICK_EVENT_NAME } from "../helpers/dom";
import ButtonIcon from "./buttonIcon";
import ButtonMenu, { ButtonMenuItemOptions } from "./buttonMenu";
import { closeBtnMenu, openBtnMenu } from "./misc";
@ -12,14 +13,13 @@ const ButtonMenuToggle = (options: Partial<{noRipple: true, onlyMobile: true}> = @@ -12,14 +13,13 @@ const ButtonMenuToggle = (options: Partial<{noRipple: true, onlyMobile: true}> =
};
const ButtonMenuToggleHandler = (el: HTMLElement) => {
(el as HTMLElement).addEventListener('click', (e) => {
(el as HTMLElement).addEventListener(CLICK_EVENT_NAME, (e) => {
//console.log('click pageIm');
if(!el.classList.contains('btn-menu-toggle')) return false;
//window.removeEventListener('mousemove', onMouseMove);
const openedMenu = el.querySelector('.btn-menu') as HTMLDivElement;
e.cancelBubble = true;
//cancelEvent(e);
cancelEvent(e);
if(el.classList.contains('menu-open')) {
closeBtnMenu();

41
src/components/chat/contextMenu.ts

@ -87,33 +87,60 @@ export default class ChatContextMenu { @@ -87,33 +87,60 @@ export default class ChatContextMenu {
const side: 'left' | 'right' = bubble.classList.contains('is-in') ? 'left' : 'right';
//bubble.parentElement.append(this.element);
//appImManager.log('contextmenu', e, bubble, side);
positionMenu(e, this.element, side);
openBtnMenu(this.element, () => {
this.peerID = this.msgID = 0;
this.target = null;
});
/////this.log('contextmenu', e, bubble, msgID, side);
};
if(isTouchSupported) {
attachTo.addEventListener('click', (e) => {
//const good = !!findUpClassName(e.target, 'message') || !!findUpClassName(e.target, 'bubble__container');
const attachClickEvent = (elem: HTMLElement, callback: (e: TouchEvent) => void) => {
elem.addEventListener('touchstart', (e) => {
const onTouchMove = (e: TouchEvent) => {
elem.removeEventListener('touchend', onTouchEnd);
};
const onTouchEnd = (e: TouchEvent) => {
elem.removeEventListener('touchmove', onTouchMove);
callback(e);
};
elem.addEventListener('touchend', onTouchEnd, {once: true});
elem.addEventListener('touchmove', onTouchMove, {once: true});
});
};
attachClickEvent(attachTo, (e) => {
if(appImManager.chatSelection.isSelecting) {
return;
}
const className = (e.target as HTMLElement).className;
if(!className || !className.includes) return;
appImManager.log('touchend', e);
const good = ['bubble', 'bubble__container', 'message', 'time', 'inner'].find(c => className.match(new RegExp(c + '($|\\s)')));
if(good) {
onContextMenu(e);
cancelEvent(e);
onContextMenu(e.changedTouches[0]);
}
});
attachContextMenuListener(attachTo, (e) => {
if(appImManager.chatSelection.isSelecting) return;
// * these two lines will fix instant text selection on iOS Safari
attachTo.classList.add('no-select');
attachTo.addEventListener('touchend', () => {
attachTo.classList.remove('no-select');
}, {once: true});
cancelSelection();
cancelEvent(e as any);
let bubble = findUpClassName(e.target, 'bubble');
//cancelEvent(e as any);
const bubble = findUpClassName(e.target, 'album-item') || findUpClassName(e.target, 'bubble');
if(bubble) {
appImManager.chatSelection.toggleByBubble(bubble);
}

4
src/components/chat/selection.ts

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
import { isTouchSupported } from "../../helpers/touchSupport";
import type { AppImManager } from "../../lib/appManagers/appImManager";
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
import { cancelEvent, cancelSelection, findUpClassName, getSelectedText } from "../../helpers/dom";
import { blurActiveElement, cancelEvent, cancelSelection, findUpClassName, getSelectedText } from "../../helpers/dom";
import Button from "../button";
import ButtonIcon from "../buttonIcon";
import CheckboxField from "../checkbox";
@ -225,6 +225,8 @@ export default class ChatSelection { @@ -225,6 +225,8 @@ export default class ChatSelection {
}
}
blurActiveElement(); // * for mobile keyboards
SetTransition(bubblesContainer, 'is-selecting', !!this.selectedMids.size, 200, () => {
if(!this.isSelecting) {
this.selectionContainer.remove();

38
src/components/misc.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import Countries, { Country, PhoneCodesMain } from "../countries";
import { cancelEvent } from "../helpers/dom";
import { cancelEvent, CLICK_EVENT_NAME } from "../helpers/dom";
import mediaSizes from "../helpers/mediaSizes";
import { clamp } from "../helpers/number";
import { isTouchSupported } from "../helpers/touchSupport";
@ -148,7 +148,7 @@ export const closeBtnMenu = () => { @@ -148,7 +148,7 @@ export const closeBtnMenu = () => {
window.removeEventListener('contextmenu', onClick);
}
document.removeEventListener('click', onClick);
document.removeEventListener(CLICK_EVENT_NAME, onClick);
};
window.addEventListener('resize', () => {
@ -186,18 +186,21 @@ export function openBtnMenu(menuElement: HTMLElement, onClose?: () => void) { @@ -186,18 +186,21 @@ export function openBtnMenu(menuElement: HTMLElement, onClose?: () => void) {
}
// ! safari iOS doesn't handle window click event on overlay, idk why
document.addEventListener('click', onClick);
document.addEventListener(CLICK_EVENT_NAME, onClick);
}
const PADDING_TOP = 8;
const PADDING_LEFT = 8;
export function positionMenu({clientX, clientY}: {clientX: number, clientY: number}/* e: MouseEvent */, elem: HTMLElement, side?: 'left' | 'right' | 'center') {
export function positionMenu({pageX, pageY}: MouseEvent | Touch, elem: HTMLElement, side?: 'left' | 'right' | 'center') {
//let {clientX, clientY} = e;
// * side mean the OPEN side
let {scrollWidth: menuWidth, scrollHeight: menuHeight} = elem;
let {innerWidth: windowWidth, innerHeight: windowHeight} = window;
//let {innerWidth: windowWidth, innerHeight: windowHeight} = window;
const rect = document.body.getBoundingClientRect();
const windowWidth = rect.width;
const windowHeight = rect.height;
side = mediaSizes.isMobile ? 'right' : 'left';
let verticalSide: 'top' /* | 'bottom' */ | 'center' = 'top';
@ -205,17 +208,17 @@ export function positionMenu({clientX, clientY}: {clientX: number, clientY: numb @@ -205,17 +208,17 @@ export function positionMenu({clientX, clientY}: {clientX: number, clientY: numb
const getSides = () => {
return {
x: {
left: clientX,
right: clientX - menuWidth
left: pageX,
right: pageX - menuWidth
},
intermediateX: side == 'right' ? PADDING_LEFT : windowWidth - menuWidth - PADDING_LEFT,
//intermediateX: clientX < windowWidth / 2 ? PADDING_LEFT : windowWidth - menuWidth - PADDING_LEFT,
y: {
top: clientY,
bottom: clientY - menuHeight
top: pageY,
bottom: pageY - menuHeight
},
//intermediateY: verticalSide == 'top' ? PADDING_TOP : windowHeight - menuHeight - PADDING_TOP,
intermediateY: clientY < windowHeight / 2 ? PADDING_TOP : windowHeight - menuHeight - PADDING_TOP,
intermediateY: pageY < windowHeight / 2 ? PADDING_TOP : windowHeight - menuHeight - PADDING_TOP,
};
};
@ -290,11 +293,13 @@ export function attachContextMenuListener(element: HTMLElement, callback: (e: To @@ -290,11 +293,13 @@ export function attachContextMenuListener(element: HTMLElement, callback: (e: To
if(isApple && isTouchSupported) {
let timeout: number;
const options: any = /* null */{capture: true};
const onCancel = () => {
clearTimeout(timeout);
element.removeEventListener('touchmove', onCancel);
element.removeEventListener('touchend', onCancel);
element.removeEventListener('touchcancel', onCancel);
element.removeEventListener('touchmove', onCancel, options);
element.removeEventListener('touchend', onCancel, options);
element.removeEventListener('touchcancel', onCancel, options);
};
element.addEventListener('touchstart', (e) => {
@ -303,11 +308,12 @@ export function attachContextMenuListener(element: HTMLElement, callback: (e: To @@ -303,11 +308,12 @@ export function attachContextMenuListener(element: HTMLElement, callback: (e: To
return;
}
element.addEventListener('touchmove', onCancel);
element.addEventListener('touchend', onCancel);
element.addEventListener('touchcancel', onCancel);
element.addEventListener('touchmove', onCancel, options);
element.addEventListener('touchend', onCancel, options);
element.addEventListener('touchcancel', onCancel, options);
timeout = window.setTimeout(() => {
element.addEventListener('touchend', cancelEvent, {once: true}); // * fix instant closing
callback(e.touches[0]);
onCancel();
}, .4e3);

18
src/components/poll.ts

@ -5,7 +5,7 @@ import appPollsManager, { Poll, PollResults } from "../lib/appManagers/appPollsM @@ -5,7 +5,7 @@ import appPollsManager, { Poll, PollResults } from "../lib/appManagers/appPollsM
import serverTimeManager from "../lib/mtproto/serverTimeManager";
import { RichTextProcessor } from "../lib/richtextprocessor";
import rootScope from "../lib/rootScope";
import { cancelEvent, findUpClassName } from "../helpers/dom";
import { cancelEvent, CLICK_EVENT_NAME, findUpClassName } from "../helpers/dom";
import { ripple } from "./ripple";
import appSidebarRight from "./sidebarRight";
@ -338,7 +338,8 @@ export default class PollElement extends HTMLElement { @@ -338,7 +338,8 @@ export default class PollElement extends HTMLElement {
this.votersCountDiv.classList.add('hide');
}
this.sendVoteBtn.addEventListener('click', () => {
this.sendVoteBtn.addEventListener(CLICK_EVENT_NAME, (e) => {
cancelEvent(e);
/* const indexes = this.answerDivs.filter(el => el.classList.contains('is-chosing')).map(el => +el.dataset.index);
if(indexes.length) {
@ -363,7 +364,7 @@ export default class PollElement extends HTMLElement { @@ -363,7 +364,7 @@ export default class PollElement extends HTMLElement {
this.performResults(results, poll.chosenIndexes);
} else if(!this.isClosed) {
this.setVotersCount(results);
this.addEventListener('click', this.clickHandler);
this.addEventListener(CLICK_EVENT_NAME, this.clickHandler);
}
}
@ -405,7 +406,7 @@ export default class PollElement extends HTMLElement { @@ -405,7 +406,7 @@ export default class PollElement extends HTMLElement {
this.descDiv.append(toggleHint);
//let active = false;
toggleHint.addEventListener('click', (e) => {
toggleHint.addEventListener(CLICK_EVENT_NAME, (e) => {
cancelEvent(e);
//active = true;
@ -425,12 +426,13 @@ export default class PollElement extends HTMLElement { @@ -425,12 +426,13 @@ export default class PollElement extends HTMLElement {
}
}
clickHandler(e: MouseEvent) {
clickHandler(e: Event) {
const target = findUpClassName(e.target, 'poll-answer') as HTMLElement;
if(!target) {
return;
}
cancelEvent(e);
const answerIndex = +target.dataset.index;
if(this.isMultiple) {
target.classList.toggle('is-chosing');
@ -512,9 +514,9 @@ export default class PollElement extends HTMLElement { @@ -512,9 +514,9 @@ export default class PollElement extends HTMLElement {
this.chosenIndexes = chosenIndexes.slice();
if(this.isRetracted) {
this.addEventListener('click', this.clickHandler);
this.addEventListener(CLICK_EVENT_NAME, this.clickHandler);
} else {
this.removeEventListener('click', this.clickHandler);
this.removeEventListener(CLICK_EVENT_NAME, this.clickHandler);
}
}

3
src/components/popup.ts

@ -1,5 +1,5 @@ @@ -1,5 +1,5 @@
import rootScope from "../lib/rootScope";
import { cancelEvent, findUpClassName } from "../helpers/dom";
import { blurActiveElement, cancelEvent, findUpClassName } from "../helpers/dom";
import { ripple } from "./ripple";
export class PopupElement {
@ -100,6 +100,7 @@ export class PopupElement { @@ -100,6 +100,7 @@ export class PopupElement {
};
public show() {
blurActiveElement(); // * hide mobile keyboard
document.body.append(this.element);
void this.element.offsetWidth; // reflow
this.element.classList.add('active');

14
src/components/sidebarLeft/index.ts

@ -8,7 +8,7 @@ import appStateManager from "../../lib/appManagers/appStateManager"; @@ -8,7 +8,7 @@ import appStateManager from "../../lib/appManagers/appStateManager";
import appUsersManager from "../../lib/appManagers/appUsersManager";
import { MOUNT_CLASS_TO } from "../../lib/mtproto/mtproto_config";
import rootScope from "../../lib/rootScope";
import { findUpClassName, findUpTag } from "../../helpers/dom";
import { CLICK_EVENT_NAME, findUpClassName, findUpTag } from "../../helpers/dom";
import AppSearch, { SearchGroup } from "../appSearch";
import "../avatar";
import { parseMenuButtonsTo, putPreloader } from "../misc";
@ -292,32 +292,32 @@ export class AppSidebarLeft extends SidebarSlider { @@ -292,32 +292,32 @@ export class AppSidebarLeft extends SidebarSlider {
this.archivedCount = this.buttons.archived.querySelector('.archived-count') as HTMLSpanElement;
this.buttons.saved.addEventListener('click', (e) => {
this.buttons.saved.addEventListener(CLICK_EVENT_NAME, (e) => {
///////this.log('savedbtn click');
setTimeout(() => { // menu doesn't close if no timeout (lol)
appImManager.setPeer(appImManager.myID);
}, 0);
});
this.buttons.archived.addEventListener('click', (e) => {
this.buttons.archived.addEventListener(CLICK_EVENT_NAME, (e) => {
this.selectTab(AppSidebarLeft.SLIDERITEMSIDS.archived);
});
this.buttons.contacts.addEventListener('click', (e) => {
this.buttons.contacts.addEventListener(CLICK_EVENT_NAME, (e) => {
this.contactsTab.openContacts();
});
this.buttons.settings.addEventListener('click', (e) => {
this.buttons.settings.addEventListener(CLICK_EVENT_NAME, (e) => {
this.settingsTab.fillElements();
this.selectTab(AppSidebarLeft.SLIDERITEMSIDS.settings);
});
this.newButtons.channel.addEventListener('click', (e) => {
this.newButtons.channel.addEventListener(CLICK_EVENT_NAME, (e) => {
this.selectTab(AppSidebarLeft.SLIDERITEMSIDS.newChannel);
});
[this.newButtons.group, this.buttons.newGroup].forEach(btn => {
btn.addEventListener('click', (e) => {
btn.addEventListener(CLICK_EVENT_NAME, (e) => {
this.addMembersTab.init(0, 'chat', false, (peerIDs) => {
this.newGroupTab.init(peerIDs);
});

8
src/components/wrappers.ts

@ -12,7 +12,7 @@ import appMessagesManager from '../lib/appManagers/appMessagesManager'; @@ -12,7 +12,7 @@ import appMessagesManager from '../lib/appManagers/appMessagesManager';
import appPhotosManager, { MyPhoto } from '../lib/appManagers/appPhotosManager';
import LottieLoader from '../lib/lottieLoader';
import VideoPlayer from '../lib/mediaPlayer';
import { isInDOM } from "../helpers/dom";
import { cancelEvent, CLICK_EVENT_NAME, isInDOM } from "../helpers/dom";
import webpWorkerController from '../lib/webp/webpWorkerController';
import animationIntersector from './animationIntersector';
import appMediaPlaybackController from './appMediaPlaybackController';
@ -373,7 +373,8 @@ export function wrapDocument(doc: MyDocument, withTime = false, uploading = fals @@ -373,7 +373,8 @@ export function wrapDocument(doc: MyDocument, withTime = false, uploading = fals
let preloader: ProgressivePreloader;
let download: DownloadBlob;
docDiv.addEventListener('click', (e) => {
docDiv.addEventListener(CLICK_EVENT_NAME, (e) => {
cancelEvent(e);
if(!download) {
if(downloadDiv.classList.contains('downloading')) {
return; // means not ready yet
@ -670,7 +671,8 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o @@ -670,7 +671,8 @@ export function wrapSticker({doc, div, middleware, lazyLoadQueue, group, play, o
}, true);
if(emoji) {
div.addEventListener('click', () => {
div.addEventListener(CLICK_EVENT_NAME, (e) => {
cancelEvent(e);
let animation = LottieLoader.getAnimation(div);
if(animation.paused) {

52
src/helpers/dom.ts

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
import { MOUNT_CLASS_TO } from "../lib/mtproto/mtproto_config";
import { isTouchSupported } from "./touchSupport";
/* export function isInDOM(element: Element, parentNode?: HTMLElement): boolean {
if(!element) {
@ -66,6 +67,38 @@ export function placeCaretAtEnd(el: HTMLElement) { @@ -66,6 +67,38 @@ export function placeCaretAtEnd(el: HTMLElement) {
}
}
/* export function getFieldSelection(field: any) {
if(field.selectionStart) {
return field.selectionStart;
// @ts-ignore
} else if(!document.selection) {
return 0;
}
const c = '\x01';
// @ts-ignore
const sel = document.selection.createRange();
const txt = sel.text;
const dup = sel.duplicate();
let len = 0;
try {
dup.moveToElementText(field);
} catch(e) {
return 0;
}
sel.text = txt + c;
len = dup.text.indexOf(c);
sel.moveStart('character', -1);
sel.text = '';
// if (browser.msie && len == -1) {
// return field.value.length
// }
return len;
} */
export function getRichValue(field: HTMLElement) {
if(!field) {
return '';
@ -90,6 +123,15 @@ MOUNT_CLASS_TO && (MOUNT_CLASS_TO.getRichValue = getRichValue); @@ -90,6 +123,15 @@ MOUNT_CLASS_TO && (MOUNT_CLASS_TO.getRichValue = getRichValue);
const markdownTags = [{
tagName: 'STRONG',
markdown: '**'
}, {
tagName: 'B', // * legacy (Ctrl+B)
markdown: '**'
}, {
tagName: 'U', // * legacy (Ctrl+I)
markdown: '_-_'
}, {
tagName: 'I', // * legacy (Ctrl+I)
markdown: '__'
}, {
tagName: 'EM',
markdown: '__'
@ -126,7 +168,7 @@ export function getRichElementValue(node: HTMLElement, lines: string[], line: st @@ -126,7 +168,7 @@ export function getRichElementValue(node: HTMLElement, lines: string[], line: st
}
}
line.push(markdown && node.nodeValue.trim() ? markdown + node.nodeValue + markdown : node.nodeValue);
line.push(markdown && node.nodeValue.trim() ? '\x01' + markdown + node.nodeValue + markdown + '\x01' : node.nodeValue);
}
return;
@ -392,3 +434,11 @@ export function getSelectedText(): string { @@ -392,3 +434,11 @@ export function getSelectedText(): string {
return '';
}
export function blurActiveElement() {
if(document.activeElement && (document.activeElement as HTMLInputElement).blur) {
(document.activeElement as HTMLInputElement).blur();
}
}
export const CLICK_EVENT_NAME = isTouchSupported ? 'touchend' : 'click';

2
src/lib/appManagers/apiUpdatesManager.ts

@ -569,7 +569,7 @@ export class ApiUpdatesManager { @@ -569,7 +569,7 @@ export class ApiUpdatesManager {
} else {
// ! for testing
/* state.seq = 1;
state.pts = state.pts - 100;
state.pts = state.pts - 1000;
state.date = 1; */
Object.assign(this.updatesState, state);

60
src/lib/appManagers/appDialogsManager.ts

@ -202,6 +202,7 @@ export class AppDialogsManager { @@ -202,6 +202,7 @@ export class AppDialogsManager {
//private topOffsetIndex = 0;
private sliceTimeout: number;
private reorderDialogsTimeout: number;
constructor() {
this.chatsPreloader = putPreloader(null, true);
@ -891,37 +892,40 @@ export class AppDialogsManager { @@ -891,37 +892,40 @@ export class AppDialogsManager {
private reorderDialogs() {
//const perf = performance.now();
let offset = 0;
if(this.topOffsetIndex) {
const element = this.chatList.firstElementChild;
if(element) {
const peerID = +element.getAttribute('data-peerID');
const firstDialog = appMessagesManager.getDialogByPeerID(peerID);
offset = firstDialog[1];
}
}
const dialogs = appMessagesManager.dialogsStorage.getFolder(this.filterID);
const currentOrder = (Array.from(this.chatList.children) as HTMLElement[]).map(el => +el.getAttribute('data-peerID'));
dialogs.forEach((dialog, index) => {
const dom = this.getDialogDom(dialog.peerID);
if(!dom) {
return;
}
const currentIndex = currentOrder[dialog.peerID];
const needIndex = index - offset;
if(currentIndex != needIndex) {
if(positionElementByIndex(dom.listEl, this.chatList, needIndex)) {
this.log.debug('setDialogPosition:', dialog, dom, needIndex);
if(this.reorderDialogsTimeout) return;
this.reorderDialogsTimeout = window.requestAnimationFrame(() => {
this.reorderDialogsTimeout = 0;
let offset = 0;
if(this.topOffsetIndex) {
const element = this.chatList.firstElementChild;
if(element) {
const peerID = +element.getAttribute('data-peerID');
const firstDialog = appMessagesManager.getDialogByPeerID(peerID);
offset = firstDialog[1];
}
}
const dialogs = appMessagesManager.dialogsStorage.getFolder(this.filterID);
const currentOrder = (Array.from(this.chatList.children) as HTMLElement[]).map(el => +el.getAttribute('data-peerID'));
dialogs.forEach((dialog, index) => {
const dom = this.getDialogDom(dialog.peerID);
if(!dom) {
return;
}
const needIndex = index - offset;
const peerIDByIndex = currentOrder[needIndex];
if(peerIDByIndex != dialog.peerID) {
if(positionElementByIndex(dom.listEl, this.chatList, needIndex)) {
this.log.debug('setDialogPosition:', dialog, dom, peerIDByIndex, needIndex);
}
}
});
//this.log('Reorder time:', performance.now() - perf);
});
//this.log('Reorder time:', performance.now() - perf);
}
public setLastMessage(dialog: any, lastMessage?: any, dom?: DialogDom, highlightWord?: string) {

29
src/lib/appManagers/appImManager.ts

@ -40,7 +40,7 @@ import apiManager from '../mtproto/mtprotoworker'; @@ -40,7 +40,7 @@ import apiManager from '../mtproto/mtprotoworker';
import { MOUNT_CLASS_TO } from '../mtproto/mtproto_config';
import { RichTextProcessor } from "../richtextprocessor";
import rootScope from '../rootScope';
import { cancelEvent, findUpClassName, findUpTag, placeCaretAtEnd, whichChild } from "../../helpers/dom";
import { cancelEvent, CLICK_EVENT_NAME, findUpClassName, findUpTag, placeCaretAtEnd, whichChild } from "../../helpers/dom";
import apiUpdatesManager from './apiUpdatesManager';
import appChatsManager, { Channel, Chat } from "./appChatsManager";
import appDialogsManager from "./appDialogsManager";
@ -708,7 +708,7 @@ export class AppImManager { @@ -708,7 +708,7 @@ export class AppImManager {
this.mutePeer(this.peerID);
});
this.btnJoin.addEventListener('click', (e) => {
this.btnJoin.addEventListener(CLICK_EVENT_NAME, (e) => {
cancelEvent(e);
this.btnJoin.setAttribute('disabled', 'true');
@ -717,11 +717,11 @@ export class AppImManager { @@ -717,11 +717,11 @@ export class AppImManager {
});
});
this.menuButtons.mute.addEventListener('click', (e) => {
this.menuButtons.mute.addEventListener(CLICK_EVENT_NAME, (e) => {
this.mutePeer(this.peerID);
});
this.menuButtons.search.addEventListener('click', (e) => {
this.menuButtons.search.addEventListener(CLICK_EVENT_NAME, (e) => {
new ChatSearch();
});
@ -745,6 +745,12 @@ export class AppImManager { @@ -745,6 +745,12 @@ export class AppImManager {
this.chatInputC.replyElements.cancelBtn.click();
} else if(this.peerID != 0) { // hide current dialog
this.setPeer(0);
cancelEvent(e);
}
// * cancel event for safari, because if application is in fullscreen, browser will try to exit fullscreen
if(this.peerID) {
cancelEvent(e);
}
} else if(e.key == 'Meta' || e.key == 'Control') {
return;
@ -785,7 +791,8 @@ export class AppImManager { @@ -785,7 +791,8 @@ export class AppImManager {
document.body.addEventListener('keydown', onKeyDown);
this.goDownBtn.addEventListener('click', () => {
this.goDownBtn.addEventListener(CLICK_EVENT_NAME, (e) => {
cancelEvent(e);
const dialog = appMessagesManager.getDialogByPeerID(this.peerID)[0];
if(dialog) {
@ -1898,7 +1905,8 @@ export class AppImManager { @@ -1898,7 +1905,8 @@ export class AppImManager {
containerDiv.append(rowDiv);
});
containerDiv.addEventListener('click', (e) => {
containerDiv.addEventListener(CLICK_EVENT_NAME, (e) => {
cancelEvent(e);
let target = e.target as HTMLElement;
if(!target.classList.contains('reply-markup-button')) target = findUpClassName(target, 'reply-markup-button');
@ -2790,8 +2798,13 @@ export class AppImManager { @@ -2790,8 +2798,13 @@ export class AppImManager {
}
public setMutedState(muted = false) {
appSidebarRight.sharedMediaTab.profileElements.notificationsCheckbox.checked = !muted;
appSidebarRight.sharedMediaTab.profileElements.notificationsStatus.innerText = muted ? 'Disabled' : 'Enabled';
if(!this.peerID) return;
const profileElements = appSidebarRight.sharedMediaTab.profileElements;
if(profileElements) {
appSidebarRight.sharedMediaTab.profileElements.notificationsCheckbox.checked = !muted;
appSidebarRight.sharedMediaTab.profileElements.notificationsStatus.innerText = muted ? 'Disabled' : 'Enabled';
}
if(appPeersManager.isBroadcast(this.peerID)) { // not human
this.btnMute.classList.remove('tgico-mute', 'tgico-unmute');

2
src/scss/components/_global.scss

@ -87,7 +87,7 @@ Utility Classes @@ -87,7 +87,7 @@ Utility Classes
}
// No Text Select
.no-select {
.no-select/* , .no-select * */ {
user-select: none;
}

1
src/scss/partials/_avatar.scss

@ -10,6 +10,7 @@ avatar-element { @@ -10,6 +10,7 @@ avatar-element {
/* overflow: hidden; */
position: relative;
user-select: none;
text-transform: uppercase;
@include respond-to(handhelds) {
font-size: 14px;

13
src/scss/partials/_button.scss

@ -106,6 +106,10 @@ @@ -106,6 +106,10 @@
transform-origin: top left;
}
&.bottom-center {
transform-origin: top center;
}
&.top-left {
top: initial;
right: 0;
@ -120,6 +124,14 @@ @@ -120,6 +124,14 @@
transform-origin: bottom left;
}
&.center-left {
transform-origin: center right;
}
&.center-right {
transform-origin: center left;
}
&-item {
display: flex;
position: relative;
@ -170,6 +182,7 @@ @@ -170,6 +182,7 @@
bottom: 0;
z-index: 1;
cursor: default;
user-select: none;
//background-color: rgba(0, 0, 0, .2);
}

13
src/scss/partials/_chatBubble.scss

@ -229,6 +229,12 @@ $bubble-margin: .25rem; @@ -229,6 +229,12 @@ $bubble-margin: .25rem;
}
}
}
#bubbles.is-selecting & {
.audio, .document, .attachment, poll-element {
pointer-events: none !important;
}
}
&__container {
//min-width: 60px;
@ -244,7 +250,12 @@ $bubble-margin: .25rem; @@ -244,7 +250,12 @@ $bubble-margin: .25rem;
height: fit-content;
z-index: 2;
transition: .2s transform;
user-select: text;
user-select: none;
html.no-touch #bubbles:not(.is-selecting) &,
html.is-touch #bubbles.is-selecting:not(.no-select) & {
user-select: text;
}
@include respond-to(not-handhelds) {
max-width: 85%;

2
src/scss/partials/_input.scss

@ -70,7 +70,7 @@ @@ -70,7 +70,7 @@
}
@include respond-to(handhelds) {
height: 50px;
min-height: 50px;
}
/* font-weight: 500; */

15
src/scss/partials/_leftSidebar.scss

@ -214,12 +214,9 @@ @@ -214,12 +214,9 @@
max-width: 78px;
width: 78px;
align-items: center;
position: relative;
display: flex;
flex-direction: column;
cursor: pointer;
padding: 12px 0 0 !important;
overflow: hidden;
margin: 0;
@include respond-to(handhelds) {
@ -233,6 +230,10 @@ @@ -233,6 +230,10 @@
height: 54px;
}
.dialog-title-details {
display: none;
}
.user-caption {
max-width: 65px;
padding: 2px 0px 9px;
@ -243,10 +244,6 @@ @@ -243,10 +244,6 @@
}
}
.user-title {
max-width: unset;
}
.search-group-scrollable {
position: relative;
@ -595,10 +592,6 @@ @@ -595,10 +592,6 @@
width: 100%;
padding: 0 16px;
}
.input-field input {
height: 50px;
}
}
.sidebar-left-h2 {

35
src/scss/style.scss

@ -198,6 +198,38 @@ $messages-container-width: 728px; @@ -198,6 +198,38 @@ $messages-container-width: 728px;
unicode-range:U + 0000-00FF, U + 0131, U + 0152-0153, U + 02BB-02BC, U + 02C6, U + 02DA, U + 02DC, U + 2000-206F, U + 2074, U + 20AC, U + 2122, U + 2191, U + 2193, U + 2212, U + 2215, U + FEFF, U + FFFD
}
// !!! FIX FOR [contenteditable] Ctrl+B, due to font-weight: 500;
/* cyrillic */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-display: swap;
src: local('Roboto Medium'), local('Roboto-Medium'), url(assets/fonts/KFOlCnqEu92Fr1MmEU9fABc4AMP6lbBP.woff2) format('woff2');
unicode-range:U + 0400-045F, U + 0490-0491, U + 04B0-04B1, U + 2116
}
/* latin-ext */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-display: swap;
src: local('Roboto Medium'), local('Roboto-Medium'), url(assets/fonts/KFOlCnqEu92Fr1MmEU9fChc4AMP6lbBP.woff2) format('woff2');
unicode-range:U + 0100-024F, U + 0259, U + 1E00-1EFF, U + 2020, U + 20A0-20AB, U + 20AD-20CF, U + 2113, U + 2C60-2C7F, U + A720-A7FF
}
/* latin */
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 700;
font-display: swap;
src: local('Roboto Medium'), local('Roboto-Medium'), url(assets/fonts/KFOlCnqEu92Fr1MmEU9fBBc4AMP6lQ.woff2) format('woff2');
unicode-range:U + 0000-00FF, U + 0131, U + 0152-0153, U + 02BB-02BC, U + 02C6, U + 02DA, U + 02DC, U + 2000-206F, U + 2074, U + 20AC, U + 2122, U + 2191, U + 2193, U + 2212, U + 2215, U + FEFF, U + FFFD
}
html, body {
height: 100%;
width: 100%;
@ -404,7 +436,8 @@ hr { @@ -404,7 +436,8 @@ hr {
.user-title, b/* , .user-last-message b */ {
color: #000;
font-weight: 500;
font-weight: bolder;
//font-weight: 500;
//font-weight: normal;
}

Loading…
Cancel
Save