Ripple changes
Chat scroll fix Date format in chat list fix Animated send button Chat input is now lockable
This commit is contained in:
parent
d4e93c819e
commit
735721fee8
@ -14,6 +14,7 @@ import { touchSupport } from "../../lib/config";
|
||||
import appDocsManager from "../../lib/appManagers/appDocsManager";
|
||||
import emoticonsDropdown from "../emoticonsDropdown";
|
||||
import PopupCreatePoll from "../popupCreatePoll";
|
||||
import { toast } from "../toast";
|
||||
|
||||
export class ChatInput {
|
||||
public pageEl = document.getElementById('page-chats') as HTMLDivElement;
|
||||
@ -95,11 +96,11 @@ export class ChatInput {
|
||||
reuseWorker: true
|
||||
});
|
||||
} catch(err) {
|
||||
this.btnSend.classList.remove('tgico-microphone2');
|
||||
this.btnSend.classList.add('tgico-send');
|
||||
console.error('Recorder constructor error:', err);
|
||||
}
|
||||
|
||||
this.updateSendBtn();
|
||||
|
||||
this.messageInput.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
if(e.key == 'Enter' && !touchSupport) {
|
||||
/* if(e.ctrlKey || e.metaKey) {
|
||||
@ -130,14 +131,14 @@ export class ChatInput {
|
||||
this.messageInput.addEventListener('input', (e) => {
|
||||
//console.log('messageInput input', this.messageInput.innerText, this.serializeNodes(Array.from(this.messageInput.childNodes)));
|
||||
|
||||
let value = this.messageInput.innerText;
|
||||
const value = this.messageInput.innerText;
|
||||
|
||||
let entities = RichTextProcessor.parseEntities(value);
|
||||
const entities = RichTextProcessor.parseEntities(value);
|
||||
//console.log('messageInput entities', entities);
|
||||
|
||||
let entityUrl = entities.find(e => e._ == 'messageEntityUrl');
|
||||
const entityUrl = entities.find(e => e._ == 'messageEntityUrl');
|
||||
if(entityUrl) { // need to get webpage
|
||||
let url = value.slice(entityUrl.offset, entityUrl.offset + entityUrl.length);
|
||||
const url = value.slice(entityUrl.offset, entityUrl.offset + entityUrl.length);
|
||||
|
||||
//console.log('messageInput url:', url);
|
||||
|
||||
@ -147,40 +148,35 @@ export class ChatInput {
|
||||
apiManager.invokeApi('messages.getWebPage', {
|
||||
url: url,
|
||||
hash: 0
|
||||
}).then((webpage: any) => {
|
||||
appWebPagesManager.saveWebPage(webpage);
|
||||
if(this.lastUrl != url) return;
|
||||
//console.log('got webpage: ', webpage);
|
||||
|
||||
this.setTopInfo(webpage.site_name || webpage.title, webpage.description || webpage.url);
|
||||
|
||||
this.replyToMsgID = 0;
|
||||
this.noWebPage = false;
|
||||
this.willSendWebPage = webpage;
|
||||
}).then((webpage) => {
|
||||
webpage = appWebPagesManager.saveWebPage(webpage);
|
||||
if(webpage._ == 'webPage') {
|
||||
if(this.lastUrl != url) return;
|
||||
//console.log('got webpage: ', webpage);
|
||||
|
||||
this.setTopInfo(webpage.site_name || webpage.title, webpage.description || webpage.url);
|
||||
|
||||
this.replyToMsgID = 0;
|
||||
this.noWebPage = false;
|
||||
this.willSendWebPage = webpage;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if(!value.trim() && !this.serializeNodes(Array.from(this.messageInput.childNodes)).trim()) {
|
||||
this.messageInput.innerHTML = '';
|
||||
if(this.recorder) {
|
||||
this.btnSend.classList.remove('tgico-send');
|
||||
this.btnSend.classList.add('tgico-microphone2');
|
||||
}
|
||||
|
||||
|
||||
appMessagesManager.setTyping('sendMessageCancelAction');
|
||||
} else if(!this.btnSend.classList.contains('tgico-send') || !this.recorder) {
|
||||
if(this.recorder) {
|
||||
this.btnSend.classList.add('tgico-send');
|
||||
this.btnSend.classList.remove('tgico-microphone2');
|
||||
}
|
||||
|
||||
let time = Date.now();
|
||||
} else {
|
||||
const time = Date.now();
|
||||
if(time - this.lastTimeType >= 6000) {
|
||||
this.lastTimeType = time;
|
||||
appMessagesManager.setTyping('sendMessageTypingAction');
|
||||
}
|
||||
}
|
||||
|
||||
this.updateSendBtn();
|
||||
});
|
||||
|
||||
if(!RichTextProcessor.emojiSupported) {
|
||||
@ -517,7 +513,7 @@ export class ChatInput {
|
||||
const onBtnSendClick = (e: Event) => {
|
||||
cancelEvent(e);
|
||||
|
||||
if(this.btnSend.classList.contains('tgico-send') || !this.recorder) {
|
||||
if(!this.recorder || this.recording || !this.isInputEmpty()) {
|
||||
if(this.recording) {
|
||||
this.recorder.stop();
|
||||
} else {
|
||||
@ -526,9 +522,9 @@ export class ChatInput {
|
||||
} else {
|
||||
this.recorder.start().then(() => {
|
||||
this.recordCanceled = false;
|
||||
this.btnSend.classList.add('tgico-send');
|
||||
this.chatInput.classList.add('is-recording');
|
||||
this.chatInput.classList.add('is-recording', 'is-locked');
|
||||
this.recording = true;
|
||||
this.updateSendBtn();
|
||||
opusDecodeController.setKeepAlive(true);
|
||||
|
||||
this.recordStartTime = Date.now();
|
||||
@ -571,7 +567,22 @@ export class ChatInput {
|
||||
|
||||
r();
|
||||
}).catch((e: Error) => {
|
||||
console.error('Recorder start error:', e);
|
||||
switch(e.name as string) {
|
||||
case 'NotAllowedError': {
|
||||
toast('Please allow access to your microphone');
|
||||
break;
|
||||
}
|
||||
|
||||
case 'NotReadableError': {
|
||||
toast(e.message);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
console.error('Recorder start error:', e, e.name, e.message);
|
||||
toast(e.message);
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
@ -591,8 +602,8 @@ export class ChatInput {
|
||||
|
||||
this.recorder.onstop = () => {
|
||||
this.recording = false;
|
||||
this.chatInput.classList.remove('is-recording');
|
||||
this.btnSend.classList.remove('tgico-send');
|
||||
this.chatInput.classList.remove('is-recording', 'is-locked');
|
||||
this.updateSendBtn();
|
||||
this.recordRippleEl.style.transform = '';
|
||||
};
|
||||
|
||||
@ -674,10 +685,7 @@ export class ChatInput {
|
||||
this.editMsgID = 0;
|
||||
this.messageInput.innerHTML = '';
|
||||
|
||||
if(this.recorder) {
|
||||
this.btnSend.classList.remove('tgico-send');
|
||||
this.btnSend.classList.add('tgico-microphone2');
|
||||
}
|
||||
this.updateSendBtn();
|
||||
}
|
||||
}
|
||||
|
||||
@ -685,6 +693,22 @@ export class ChatInput {
|
||||
this.willSendWebPage = null;
|
||||
});
|
||||
}
|
||||
|
||||
private isInputEmpty() {
|
||||
let value = this.messageInput.innerText;
|
||||
|
||||
return !value.trim() && !this.serializeNodes(Array.from(this.messageInput.childNodes)).trim();
|
||||
}
|
||||
|
||||
public updateSendBtn() {
|
||||
let icon: 'send' | 'record';
|
||||
|
||||
if(!this.recorder || this.recording || !this.isInputEmpty()) icon = 'send';
|
||||
else icon = 'record';
|
||||
|
||||
this.btnSend.classList.toggle('send', icon == 'send');
|
||||
this.btnSend.classList.toggle('record', icon == 'record');
|
||||
}
|
||||
|
||||
public serializeNodes(nodes: Node[]): string {
|
||||
return nodes.reduce((str, child: any) => {
|
||||
@ -711,10 +735,7 @@ export class ChatInput {
|
||||
this.willSendWebPage = null;
|
||||
this.messageInput.innerText = '';
|
||||
|
||||
if(this.recorder) {
|
||||
this.btnSend.classList.remove('tgico-send');
|
||||
this.btnSend.classList.add('tgico-microphone2');
|
||||
}
|
||||
this.updateSendBtn();
|
||||
}
|
||||
|
||||
if(clearReply || clearInput) {
|
||||
@ -776,8 +797,7 @@ export class ChatInput {
|
||||
if(input !== undefined) {
|
||||
this.messageInput.innerHTML = input ? RichTextProcessor.wrapRichText(input) : '';
|
||||
|
||||
this.btnSend.classList.remove('tgico-microphone2');
|
||||
this.btnSend.classList.add('tgico-send');
|
||||
this.updateSendBtn();
|
||||
}
|
||||
|
||||
//appImManager.scrollPosition.restore();
|
||||
|
@ -1,3 +1,4 @@
|
||||
import mediaSizes from "../helpers/mediaSizes";
|
||||
import { touchSupport } from "../lib/config";
|
||||
import { findUpClassName } from "../lib/utils";
|
||||
|
||||
@ -15,20 +16,22 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise<bool
|
||||
r.classList.add('is-square');
|
||||
}
|
||||
|
||||
const duration = isSquare ? 200 : 700;
|
||||
|
||||
elem.append(r);
|
||||
|
||||
let handler: () => void;
|
||||
let drawRipple = (clientX: number, clientY: number) => {
|
||||
let startTime = Date.now();
|
||||
let span = document.createElement('span');
|
||||
let animationEndPromise: Promise<any>;
|
||||
const drawRipple = (clientX: number, clientY: number) => {
|
||||
const startTime = Date.now();
|
||||
const span = document.createElement('span');
|
||||
|
||||
let clickID = rippleClickID++;
|
||||
const clickID = rippleClickID++;
|
||||
|
||||
//console.log('ripple drawRipple');
|
||||
|
||||
let duration: number;
|
||||
|
||||
handler = () => {
|
||||
//const duration = isSquare || mediaSizes.isMobile ? 200 : 700;
|
||||
//return;
|
||||
let elapsedTime = Date.now() - startTime;
|
||||
if(elapsedTime < duration) {
|
||||
@ -96,8 +99,16 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise<bool
|
||||
span.style.width = span.style.height = size + 'px';
|
||||
span.style.left = x + 'px';
|
||||
span.style.top = y + 'px';
|
||||
|
||||
|
||||
|
||||
animationEndPromise = new Promise((resolve) => {
|
||||
span.addEventListener('animationend', resolve, {once: true});
|
||||
});
|
||||
|
||||
r.append(span);
|
||||
|
||||
duration = +window.getComputedStyle(span).getPropertyValue('animation-duration').replace('s', '') * 1000;
|
||||
|
||||
//r.classList.add('active');
|
||||
//handler();
|
||||
});
|
||||
@ -121,12 +132,12 @@ export function ripple(elem: HTMLElement, callback: (id: number) => Promise<bool
|
||||
|
||||
let {clientX, clientY} = e.touches[0];
|
||||
drawRipple(clientX, clientY);
|
||||
window.addEventListener('touchend', touchEnd, {once: true});
|
||||
elem.addEventListener('touchend', touchEnd, {once: true});
|
||||
|
||||
window.addEventListener('touchmove', (e) => {
|
||||
e.cancelBubble = true;
|
||||
e.stopPropagation();
|
||||
handler && handler();
|
||||
touchEnd();
|
||||
window.removeEventListener('touchend', touchEnd);
|
||||
}, {once: true});
|
||||
}, {passive: true});
|
||||
|
@ -20,6 +20,7 @@ import { PhotoSize } from '../layer';
|
||||
import { deferredPromise } from '../helpers/cancellablePromise';
|
||||
import mediaSizes from '../helpers/mediaSizes';
|
||||
import { isSafari } from '../helpers/userAgent';
|
||||
import { months } from '../helpers/date';
|
||||
|
||||
export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTail, isOut, middleware, lazyLoadQueue, noInfo, group}: {
|
||||
doc: MyDocument,
|
||||
@ -279,7 +280,6 @@ export function wrapVideo({doc, container, message, boxWidth, boxHeight, withTai
|
||||
}
|
||||
|
||||
export const formatDate = (timestamp: number, monthShort = false, withYear = true) => {
|
||||
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'Octomber', 'November', 'December'];
|
||||
const date = new Date(timestamp * 1000);
|
||||
|
||||
let month = months[date.getMonth()];
|
||||
|
32
src/helpers/date.ts
Normal file
32
src/helpers/date.ts
Normal file
@ -0,0 +1,32 @@
|
||||
export const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
||||
export const days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||
|
||||
const ONE_DAY = 86400e3;
|
||||
|
||||
// https://stackoverflow.com/a/6117889
|
||||
export const getWeekNumber = (date: Date) => {
|
||||
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
||||
const dayNum = d.getUTCDay() || 7;
|
||||
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
|
||||
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
||||
return Math.ceil((((d.getTime() - yearStart.getTime()) / ONE_DAY) + 1) / 7);
|
||||
};
|
||||
|
||||
export const formatDateAccordingToToday = (time: Date) => {
|
||||
const date = new Date();
|
||||
const now = date.getTime() / 1000;
|
||||
const timestamp = date.getTime() / 1000;
|
||||
|
||||
let timeStr: string;
|
||||
if((now - timestamp) < ONE_DAY && date.getDate() == time.getDate()) { // if the same day
|
||||
timeStr = ('0' + time.getHours()).slice(-2) + ':' + ('0' + time.getMinutes()).slice(-2);
|
||||
} else if((now - timestamp) < (ONE_DAY * 7) && getWeekNumber(date) == getWeekNumber(time)) { // current week
|
||||
timeStr = days[time.getDay()].slice(0, 3);
|
||||
} else if(date.getFullYear() != time.getFullYear()) { // different year
|
||||
timeStr = time.getDate() + '.' + (time.getMonth() + 1) + '.' + time.getFullYear();
|
||||
} else { // same year
|
||||
timeStr = months[time.getMonth()].slice(0, 3) + ' ' + ('0' + time.getDate()).slice(-2);
|
||||
}
|
||||
|
||||
return timeStr;
|
||||
};
|
@ -80,7 +80,7 @@ class MediaSizes extends EventListenerBase<{
|
||||
}
|
||||
}
|
||||
|
||||
if(this.activeScreen != activeScreen) {
|
||||
if(this.activeScreen != activeScreen && this.activeScreen !== undefined) {
|
||||
//console.log('changeScreen', this.activeScreen, activeScreen);
|
||||
this.setListenerResult('changeScreen', this.activeScreen, activeScreen);
|
||||
}
|
||||
@ -102,4 +102,7 @@ class MediaSizes extends EventListenerBase<{
|
||||
}
|
||||
|
||||
const mediaSizes = new MediaSizes();
|
||||
if(process.env.NODE_ENV != 'production') {
|
||||
(window as any).mediaSizes = mediaSizes;
|
||||
}
|
||||
export default mediaSizes;
|
@ -452,11 +452,11 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-menu" id="dialogs-contextmenu">
|
||||
<div class="btn-menu-item menu-unread tgico rp"></div>
|
||||
<div class="btn-menu-item menu-pin tgico rp"></div>
|
||||
<div class="btn-menu-item menu-mute tgico rp"></div>
|
||||
<div class="btn-menu-item menu-archive tgico rp"></div>
|
||||
<div class="btn-menu-item menu-delete tgico-delete danger rp"></div>
|
||||
<div class="btn-menu-item menu-unread tgico rp"><div></div></div>
|
||||
<div class="btn-menu-item menu-pin tgico rp"><div></div></div>
|
||||
<div class="btn-menu-item menu-mute tgico rp"><div></div></div>
|
||||
<div class="btn-menu-item menu-archive tgico rp"><div></div></div>
|
||||
<div class="btn-menu-item menu-delete tgico-delete danger rp"><div></div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-container main-column" id="column-center">
|
||||
@ -492,8 +492,8 @@
|
||||
</div>
|
||||
<div id="bubbles" class="scrolled-down">
|
||||
<div id="bubbles-inner"></div>
|
||||
<div id="bubbles-go-down" class="tgico-down btn-corner z-depth-1 rp hide"></div>
|
||||
</div>
|
||||
<div id="bubbles-go-down" class="tgico-down btn-corner z-depth-1 rp hide"></div>
|
||||
<div class="btn-menu" id="bubble-contextmenu">
|
||||
<div class="btn-menu-item menu-reply tgico-reply rp">Reply</div>
|
||||
<div class="btn-menu-item menu-edit tgico-edit rp">Edit</div>
|
||||
@ -535,7 +535,10 @@
|
||||
<button class="btn-circle z-depth-1 btn-icon tgico-delete danger" id="btn-record-cancel"></button>
|
||||
<div class="btn-send-container">
|
||||
<div class="record-ripple"></div>
|
||||
<button class="btn-circle z-depth-1 btn-icon tgico-microphone2" id="btn-send"></button>
|
||||
<button class="btn-circle rp z-depth-1 btn-icon" id="btn-send">
|
||||
<span class="tgico tgico-send"></span>
|
||||
<span class="tgico tgico-microphone2"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="emoji-dropdown" id="emoji-dropdown" style="display: none;">
|
||||
|
@ -17,6 +17,7 @@ import { touchSupport } from "../config";
|
||||
import { horizontalMenu } from "../../components/horizontalMenu";
|
||||
import { ripple } from "../../components/ripple";
|
||||
import { isSafari } from "../../helpers/userAgent";
|
||||
import { formatDateAccordingToToday, getWeekNumber } from "../../helpers/date";
|
||||
|
||||
type DialogDom = {
|
||||
avatarEl: AvatarElement,
|
||||
@ -197,7 +198,7 @@ class DialogsContextMenu {
|
||||
const button = this.buttons.archive;
|
||||
const condition = dialog.folder_id == 1;
|
||||
button.classList.toggle('flip-icon', condition);
|
||||
button.innerText = condition ? 'Unarchive' : 'Archive';
|
||||
(button.firstElementChild as HTMLElement).innerText = condition ? 'Unarchive' : 'Archive';
|
||||
this.buttons.archive.style.display = '';
|
||||
} else {
|
||||
this.buttons.archive.style.display = 'none';
|
||||
@ -209,7 +210,7 @@ class DialogsContextMenu {
|
||||
//const condition = !!dialog.pFlags?.pinned;
|
||||
const condition = this.filterID > 1 ? appMessagesManager.filtersStorage.filters[this.filterID].pinned_peers.includes(dialog.peerID) : !!dialog.pFlags?.pinned;
|
||||
button.classList.toggle('flip-icon', condition);
|
||||
button.innerText = condition ? 'Unpin' : 'Pin';
|
||||
(button.firstElementChild as HTMLElement).innerText = condition ? 'Unpin' : 'Pin';
|
||||
}
|
||||
|
||||
// mute button
|
||||
@ -217,7 +218,7 @@ class DialogsContextMenu {
|
||||
const button = this.buttons.mute;
|
||||
const condition = dialog.notify_settings && dialog.notify_settings.mute_until > (Date.now() / 1000 | 0);
|
||||
button.classList.toggle('flip-icon', condition);
|
||||
button.innerText = condition ? 'Unmute' : 'Mute';
|
||||
(button.firstElementChild as HTMLElement).innerText = condition ? 'Unmute' : 'Mute';
|
||||
this.buttons.mute.style.display = '';
|
||||
} else {
|
||||
this.buttons.mute.style.display = 'none';
|
||||
@ -228,7 +229,7 @@ class DialogsContextMenu {
|
||||
const button = this.buttons.unread;
|
||||
const condition = !!(dialog.pFlags?.unread_mark || dialog.unread_count);
|
||||
button.classList.toggle('flip-icon', condition);
|
||||
button.innerText = condition ? 'Mark as Read' : 'Mark as Unread';
|
||||
(button.firstElementChild as HTMLElement).innerText = condition ? 'Mark as Read' : 'Mark as Unread';
|
||||
}
|
||||
|
||||
/* // clear history button
|
||||
@ -257,7 +258,7 @@ class DialogsContextMenu {
|
||||
//deleteButtonText = 'Delete chat';
|
||||
this.peerType = this.selectedID == $rootScope.myID ? 'saved' : 'chat';
|
||||
}
|
||||
this.buttons.delete.innerText = deleteButtonText;
|
||||
(this.buttons.delete.firstElementChild as HTMLElement).innerText = deleteButtonText;
|
||||
|
||||
li.classList.add('menu-open');
|
||||
positionMenu(e, this.element);
|
||||
@ -1001,25 +1002,7 @@ export class AppDialogsManager {
|
||||
}
|
||||
|
||||
if(!lastMessage.deleted) {
|
||||
let timeStr = '';
|
||||
let timestamp = lastMessage.date;
|
||||
let now = Date.now() / 1000;
|
||||
let time = new Date(lastMessage.date * 1000);
|
||||
|
||||
if((now - timestamp) < 86400) { // if < 1 day
|
||||
timeStr = ('0' + time.getHours()).slice(-2) +
|
||||
':' + ('0' + time.getMinutes()).slice(-2);
|
||||
} else if((now - timestamp) < (86400 * 7)) { // week
|
||||
let date = new Date(timestamp * 1000);
|
||||
timeStr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'][date.getDay()];
|
||||
} else {
|
||||
let months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
||||
|
||||
timeStr = months[time.getMonth()] +
|
||||
' ' + ('0' + time.getDate()).slice(-2);
|
||||
}
|
||||
|
||||
dom.lastTimeSpan.innerHTML = timeStr;
|
||||
dom.lastTimeSpan.innerHTML = formatDateAccordingToToday(new Date(lastMessage.date * 1000));
|
||||
} else dom.lastTimeSpan.innerHTML = '';
|
||||
|
||||
if(this.doms[peerID] == dom) {
|
||||
|
@ -40,7 +40,7 @@ import { ChatAudio } from '../../components/chat/audio';
|
||||
import { ChatContextMenu } from '../../components/chat/contextMenu';
|
||||
import { ChatSearch } from '../../components/chat/search';
|
||||
import mediaSizes from '../../helpers/mediaSizes';
|
||||
import { isAndroid, isApple } from '../../helpers/userAgent';
|
||||
import { isAndroid, isApple, isSafari } from '../../helpers/userAgent';
|
||||
|
||||
//console.log('appImManager included33!');
|
||||
|
||||
@ -847,6 +847,8 @@ export class AppImManager {
|
||||
|
||||
this.scrollable = new Scrollable(this.bubblesContainer, 'y', 'IM', this.chatInner, getScrollOffset()); */
|
||||
this.scroll = this.scrollable.container;
|
||||
|
||||
this.bubblesContainer.append(this.goDownBtn);
|
||||
|
||||
this.scrollable.onScrolledTop = () => this.loadMoreHistory(true);
|
||||
this.scrollable.onScrolledBottom = () => this.loadMoreHistory(false);
|
||||
@ -1045,7 +1047,7 @@ export class AppImManager {
|
||||
this.scrollable.scrollIntoView(bubble);
|
||||
this.highlightBubble(bubble);
|
||||
} else if(dialog && lastMsgID == topMessage) {
|
||||
this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight);
|
||||
//this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight);
|
||||
this.scroll.scrollTop = this.scroll.scrollHeight;
|
||||
}
|
||||
|
||||
@ -1225,6 +1227,7 @@ export class AppImManager {
|
||||
|
||||
const isAnyGroup = appPeersManager.isAnyGroup(peerID);
|
||||
const isChannel = appPeersManager.isChannel(peerID);
|
||||
const isBroadcast = appPeersManager.isBroadcast(peerID);
|
||||
const hasRights = isChannel && appChatsManager.hasRights(-peerID, 'send');
|
||||
this.chatInner.classList.toggle('has-rights', hasRights);
|
||||
|
||||
@ -1232,11 +1235,12 @@ export class AppImManager {
|
||||
|
||||
this.topbar.classList.remove('is-pinned-shown');
|
||||
this.topbar.style.display = '';
|
||||
|
||||
|
||||
this.chatInner.classList.toggle('is-chat', isAnyGroup || peerID == this.myID);
|
||||
this.chatInner.classList.toggle('is-channel', isChannel);
|
||||
this.goDownBtn.classList.toggle('is-broadcast', isBroadcast);
|
||||
|
||||
this.btnMute.classList.toggle('hide', !appPeersManager.isBroadcast(peerID));
|
||||
this.btnMute.classList.toggle('hide', !isBroadcast);
|
||||
this.btnJoin.classList.toggle('hide', !appChatsManager.getChat(-this.peerID)?.pFlags?.left);
|
||||
|
||||
this.menuButtons.mute.style.display = this.myID == this.peerID ? 'none' : '';
|
||||
@ -1324,12 +1328,12 @@ export class AppImManager {
|
||||
if(this.messagesQueuePromise && scrolledDown) {
|
||||
this.scrollable.scrollTo(this.scrollable.scrollHeight - 1, 'top', false, true);
|
||||
this.messagesQueuePromise.then(() => {
|
||||
this.log('messagesQueuePromise after:', this.chatInner.childElementCount, this.scrollable.scrollHeight);
|
||||
//this.log('messagesQueuePromise after:', this.chatInner.childElementCount, this.scrollable.scrollHeight);
|
||||
this.scrollable.scrollTo(this.scrollable.scrollHeight, 'top', true, true);
|
||||
|
||||
setTimeout(() => {
|
||||
/* setTimeout(() => {
|
||||
this.log('messagesQueuePromise afterafter:', this.chatInner.childElementCount, this.scrollable.scrollHeight);
|
||||
}, 10);
|
||||
}, 10); */
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -1404,6 +1408,8 @@ export class AppImManager {
|
||||
else dateMessage.container.append(bubble);
|
||||
return; */
|
||||
|
||||
//this.log('renderMessagesQueue');
|
||||
|
||||
let promises: Promise<any>[] = [];
|
||||
(Array.from(bubble.querySelectorAll('img, video')) as HTMLImageElement[]).forEach(el => {
|
||||
if(el instanceof HTMLVideoElement) {
|
||||
@ -1450,12 +1456,12 @@ export class AppImManager {
|
||||
if(!this.messagesQueuePromise) {
|
||||
this.messagesQueuePromise = new Promise((resolve, reject) => {
|
||||
setTimeout(() => {
|
||||
let chatInner = this.chatInner;
|
||||
let queue = this.messagesQueue.slice();
|
||||
const chatInner = this.chatInner;
|
||||
const queue = this.messagesQueue.slice();
|
||||
this.messagesQueue.length = 0;
|
||||
|
||||
let promises = queue.reduce((acc, {promises}) => acc.concat(promises), []);
|
||||
//console.log('promises to call', promises, queue);
|
||||
const promises = queue.reduce((acc, {promises}) => acc.concat(promises), []);
|
||||
//this.log('promises to call', promises, queue);
|
||||
Promise.all(promises).then(() => {
|
||||
if(this.chatInner != chatInner) {
|
||||
//this.log.warn('chatInner changed!', this.chatInner, chatInner);
|
||||
@ -1467,13 +1473,11 @@ export class AppImManager {
|
||||
}
|
||||
|
||||
queue.forEach(({message, bubble, reverse}) => {
|
||||
let dateMessage = this.getDateContainerByMessage(message, reverse);
|
||||
const dateMessage = this.getDateContainerByMessage(message, reverse);
|
||||
if(reverse) {
|
||||
dateMessage.container.insertBefore(bubble, dateMessage.div.nextSibling);
|
||||
//this.scrollable.prepareElement(bubble, false);
|
||||
} else {
|
||||
dateMessage.container.append(bubble);
|
||||
//this.scrollable.prepareElement(bubble, true);
|
||||
}
|
||||
});
|
||||
|
||||
@ -2315,24 +2319,26 @@ export class AppImManager {
|
||||
//console.time('appImManager render history');
|
||||
|
||||
return new Promise<boolean>((resolve, reject) => {
|
||||
let method = (reverse ? history.shift : history.pop).bind(history);
|
||||
//await new Promise((resolve) => setTimeout(resolve, 1e3));
|
||||
|
||||
let realLength = this.scrollable.length;
|
||||
//this.log('performHistoryResult: will render some messages:', history.length);
|
||||
|
||||
const method = (reverse ? history.shift : history.pop).bind(history);
|
||||
|
||||
const realLength = this.scrollable.length;
|
||||
let previousScrollHeightMinusTop: number;
|
||||
if(realLength > 0 && reverse) { // for safari need set when scrolling bottom too
|
||||
if(realLength > 0 && (reverse || isSafari)) { // for safari need set when scrolling bottom too
|
||||
this.messagesQueueOnRender = () => {
|
||||
let scrollTop = this.scrollable.scrollTop;
|
||||
const {scrollTop, scrollHeight} = this.scrollable;
|
||||
|
||||
previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop;
|
||||
//this.chatInner.style.height = '100%';
|
||||
//previousScrollHeightMinusTop = 0;
|
||||
previousScrollHeightMinusTop = reverse ? scrollHeight - scrollTop : scrollTop;
|
||||
/* if(reverse) {
|
||||
previousScrollHeightMinusTop = this.scrollable.scrollHeight - scrollTop;
|
||||
} else {
|
||||
previousScrollHeightMinusTop = scrollTop;
|
||||
} */
|
||||
|
||||
this.log('performHistoryResult: messagesQueueOnRender, scrollTop:', scrollTop, previousScrollHeightMinusTop);
|
||||
//this.log('performHistoryResult: messagesQueueOnRender, scrollTop:', scrollTop, scrollHeight, previousScrollHeightMinusTop);
|
||||
this.messagesQueueOnRender = undefined;
|
||||
};
|
||||
}
|
||||
@ -2342,15 +2348,20 @@ export class AppImManager {
|
||||
this.renderMessage(message, reverse, true);
|
||||
}
|
||||
|
||||
(this.messagesQueuePromise || Promise.resolve()).then(() => {
|
||||
(this.messagesQueuePromise || Promise.resolve())
|
||||
//.then(() => new Promise(resolve => setTimeout(resolve, 100)))
|
||||
.then(() => {
|
||||
if(previousScrollHeightMinusTop !== undefined) {
|
||||
const newScrollTop = reverse ? this.scrollable.scrollHeight - previousScrollHeightMinusTop : previousScrollHeightMinusTop;
|
||||
this.log('performHistoryResult: will set scrollTop', this.scrollable.scrollHeight, newScrollTop, this.scrollable.container.clientHeight);
|
||||
//this.log('performHistoryResult: will set scrollTop', this.scrollable.scrollHeight, newScrollTop, this.scrollable.container.clientHeight);
|
||||
|
||||
// touchSupport for safari iOS
|
||||
touchSupport && isApple && (this.scrollable.container.style.overflow = 'hidden');
|
||||
this.scrollable.scrollTop = newScrollTop;
|
||||
//this.scrollable.scrollTop = this.scrollable.scrollHeight;
|
||||
touchSupport && isApple && (this.scrollable.container.style.overflow = '');
|
||||
|
||||
//this.log('performHistoryResult: have set up scrollTop:', newScrollTop, this.scrollable.scrollTop);
|
||||
}
|
||||
|
||||
resolve(true);
|
||||
@ -2460,7 +2471,7 @@ export class AppImManager {
|
||||
|
||||
//let removeCount = loadCount / 2;
|
||||
const safeCount = realLoadCount * 2; // cause i've been runningrunningrunning all day
|
||||
this.log('getHistory: slice loadedTimes:', reverse, pageCount, this.loadedTopTimes, this.loadedBottomTimes, ids && ids.length, safeCount);
|
||||
this.log('getHistory: slice loadedTimes:', reverse, pageCount, this.loadedTopTimes, this.loadedBottomTimes, ids?.length, safeCount);
|
||||
if(ids && ids.length > safeCount) {
|
||||
if(reverse) {
|
||||
//ids = ids.slice(-removeCount);
|
||||
|
@ -80,7 +80,7 @@ export class AppSidebarRight extends SidebarSlider {
|
||||
|
||||
public toggleSidebar(enable?: boolean) {
|
||||
/////this.log('sidebarEl', this.sidebarEl, enable, isElementInViewport(this.sidebarEl));
|
||||
|
||||
|
||||
const active = document.body.classList.contains(COLUMN_ACTIVE_CLASSNAME);
|
||||
let willChange: boolean;
|
||||
if(enable !== undefined) {
|
||||
@ -107,17 +107,13 @@ export class AppSidebarRight extends SidebarSlider {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const set = () => {
|
||||
document.body.classList.toggle(COLUMN_ACTIVE_CLASSNAME, enable);
|
||||
};
|
||||
|
||||
set();
|
||||
document.body.classList.toggle(COLUMN_ACTIVE_CLASSNAME, enable);
|
||||
return new Promise(resolve => {
|
||||
setTimeout(resolve, 200);
|
||||
});
|
||||
//return Promise.resolve();
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
/* return new Promise((resolve, reject) => {
|
||||
const hidden: {element: HTMLDivElement, height: number}[] = [];
|
||||
const observer = new IntersectionObserver((entries) => {
|
||||
for(const entry of entries) {
|
||||
@ -145,11 +141,6 @@ export class AppSidebarRight extends SidebarSlider {
|
||||
(item.element.firstElementChild as HTMLElement).style.display = '';
|
||||
}
|
||||
|
||||
/* if(active) {
|
||||
appForward.close();
|
||||
this.searchCloseBtn.click();
|
||||
} */
|
||||
|
||||
resolve();
|
||||
}, 200);
|
||||
});
|
||||
@ -163,7 +154,7 @@ export class AppSidebarRight extends SidebarSlider {
|
||||
set();
|
||||
setTimeout(resolve, 200);
|
||||
}
|
||||
});
|
||||
}); */
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -101,6 +101,8 @@ class AppWebPagesManager {
|
||||
msgs: msgs
|
||||
});
|
||||
}
|
||||
|
||||
return apiWebPage;
|
||||
}
|
||||
|
||||
public getWebPage(id: string) {
|
||||
|
@ -298,9 +298,19 @@
|
||||
#btn-send {
|
||||
color: #9e9e9e;
|
||||
|
||||
&.tgico-send {
|
||||
> .tgico {
|
||||
position: absolute;
|
||||
animation: hide-icon .4s forwards ease-in-out;
|
||||
}
|
||||
|
||||
&.send {
|
||||
color: $color-blue;
|
||||
}
|
||||
|
||||
&.send .tgico-send,
|
||||
&.record .tgico-microphone2 {
|
||||
animation: grow-icon .4s forwards ease-in-out;
|
||||
}
|
||||
}
|
||||
|
||||
#btn-record-cancel, #btn-send {
|
||||
@ -345,6 +355,14 @@
|
||||
left: -124px;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-locked {
|
||||
pointer-events: none;
|
||||
|
||||
.btn-icon {
|
||||
color: #c6cbce;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-recording {
|
||||
#btn-record-cancel {
|
||||
@ -354,6 +372,11 @@
|
||||
transition: width .1s, margin-right .1s, visibility 0s .1s, opacity .1s .1s;
|
||||
}
|
||||
|
||||
// unlock
|
||||
#btn-send, #btn-record-cancel {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
#attach-file {
|
||||
display: none;
|
||||
}
|
||||
@ -568,13 +591,12 @@
|
||||
|
||||
.btn-icon {
|
||||
display: block;
|
||||
transition: .2s color;
|
||||
transition: .2s color, background-color .2s;
|
||||
flex: 0 0 auto;
|
||||
|
||||
font-size: 24px;
|
||||
line-height: 24px;
|
||||
padding: 10px 7px 9px 7.5px;
|
||||
color: #8d969c;
|
||||
margin-bottom: 1px;
|
||||
|
||||
&.active {
|
||||
color: $color-blue;
|
||||
@ -887,12 +909,25 @@
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
flex: 1 1 auto; /* Lets middle column shrink/grow to available width */
|
||||
overflow: hidden;
|
||||
//overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
html.is-safari & {
|
||||
-webkit-mask-image: -webkit-radial-gradient(circle, white 100%, black 100%); // fix safari overflow
|
||||
}
|
||||
}
|
||||
|
||||
// ! WARNING, НЕЛЬЗЯ СТАВИТЬ ТРАНСФОРМ КРОМЕ TRANSLATEZ(0) НА БЛОК С OVERFLOW, ОН БУДЕТ ПРЫГАТЬ ВВЕРХ ПРИ ВКЛЮЧЕННОМ ПРАВИЛЕ И ЭТО НЕ ИСПРАВИТЬ JS'ОМ!
|
||||
@include respond-to(medium-screens) {
|
||||
transition: transform var(--layer-transition);
|
||||
|
||||
body.is-right-column-shown & {
|
||||
transform: translate3d(calc(var(--right-column-width) / -2), 0, 0);
|
||||
}
|
||||
|
||||
body.animation-level-0 & {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
|
||||
> .scrollable {
|
||||
height: auto;
|
||||
@ -908,46 +943,26 @@
|
||||
/* display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-end; */
|
||||
|
||||
// * scrollbar takes some width, don't need to set padding for iOS
|
||||
html.is-safari:not(.is-ios) & {
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
@include respond-to(medium-screens) {
|
||||
transition: transform var(--layer-transition);
|
||||
|
||||
body.is-right-column-shown & {
|
||||
transform: translate3d(calc(var(--right-column-width) / -2), 0, 0);
|
||||
}
|
||||
|
||||
body.animation-level-0 & {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
> .preloader-container {
|
||||
@include respond-to(medium-screens) {
|
||||
transition: transform var(--layer-transition);
|
||||
|
||||
body.is-right-column-shown & {
|
||||
transform: translate3d(calc(var(--right-column-width) / -2), 0, 0);
|
||||
}
|
||||
|
||||
body.animation-level-0 & {
|
||||
transition: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.scrolled-down):not(.search-results-active) {
|
||||
-webkit-mask-image: -webkit-linear-gradient(bottom, transparent, #000 20px);
|
||||
mask-image: linear-gradient(0deg, transparent 0, #000 20px);
|
||||
> .scrollable {
|
||||
-webkit-mask-image: -webkit-linear-gradient(bottom, transparent, #000 20px);
|
||||
mask-image: linear-gradient(0deg, transparent 0, #000 20px);
|
||||
}
|
||||
|
||||
& + #bubbles-go-down {
|
||||
> #bubbles-go-down {
|
||||
cursor: pointer;
|
||||
--translateY: 0;
|
||||
opacity: 1;
|
||||
|
||||
/* &.is-broadcast {
|
||||
--translateY: 79px !important;
|
||||
} */
|
||||
}
|
||||
}
|
||||
|
||||
@ -987,7 +1002,7 @@
|
||||
}
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
padding: 0 .5rem;
|
||||
padding: 0 .75rem 0 .5rem;
|
||||
|
||||
html.is-mac & {
|
||||
-webkit-user-select: none;
|
||||
@ -1045,16 +1060,15 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
right: 17.5px;
|
||||
//bottom: 17.5px;
|
||||
bottom: 96.5px;
|
||||
cursor: pointer;
|
||||
bottom: 17.5px;
|
||||
cursor: default;
|
||||
opacity: 0;
|
||||
user-select: none;
|
||||
z-index: 2;
|
||||
transition: var(--btn-corner-transition), opacity .2s;
|
||||
overflow: hidden;
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
bottom: 75.5px;
|
||||
right: .5rem;
|
||||
width: 2.875rem;
|
||||
height: 2.875rem;
|
||||
@ -1068,9 +1082,13 @@
|
||||
transition: transform var(--layer-transition), opacity .2s;
|
||||
|
||||
body.is-right-column-shown & {
|
||||
transform: translate3d(calc(var(--right-column-width) * -1), var(--translateY), 0);
|
||||
transform: translate3d(calc(var(--right-column-width) * -.5), var(--translateY), 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* &.is-broadcast {
|
||||
--translateY: 99px !important;
|
||||
} */
|
||||
}
|
||||
|
||||
.popup {
|
||||
@ -1154,11 +1172,11 @@
|
||||
z-index: 5;
|
||||
top: 8px;
|
||||
align-items: center;
|
||||
transform: translateY(calc(-100% - 10px));
|
||||
transform: translate3d(0, calc(-100% - 10px), 0);
|
||||
transition: transform .2s ease;
|
||||
|
||||
&.active {
|
||||
transform: translateY(0);
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
|
||||
.container {
|
||||
|
@ -50,17 +50,17 @@
|
||||
transition: .35s opacity;
|
||||
//overflow: hidden;
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
.btn-menu &, .c-ripple.is-square & {
|
||||
animation-duration: .2s;
|
||||
transition-duration: .1s;
|
||||
border-radius: 15%;
|
||||
//border-radius: 15%;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-square .c-ripple__circle, .btn-menu & .c-ripple__circle {
|
||||
animation-duration: .2s;
|
||||
transition-duration: .1s;
|
||||
border-radius: 15%;
|
||||
/* @include respond-to(handhelds) {
|
||||
animation-duration: .2s;
|
||||
transition-duration: .1s;
|
||||
//border-radius: 15%;
|
||||
} */
|
||||
}
|
||||
|
||||
&__circle.hiding, &__square.hiding {
|
||||
@ -68,6 +68,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
@include respond-to(handhelds) {
|
||||
.chats-container ul li > .rp .c-ripple__circle {
|
||||
animation-duration: .2s;
|
||||
transition-duration: .1s;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ripple-effect {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
|
@ -99,6 +99,7 @@
|
||||
cursor: pointer;
|
||||
color: $color-blue;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
html.no-touch &:hover {
|
||||
background-color: rgba(112, 117, 121, 0.08);
|
||||
|
@ -54,9 +54,9 @@ $large-screen: 1680px;
|
||||
:root {
|
||||
--color-gray: #c4c9cc;
|
||||
--layer-transition: .2s ease-in-out;
|
||||
--btn-corner-transition: transform .2s cubic-bezier(.34, 1.56, .64, 1);
|
||||
//--layer-transition: .3s cubic-bezier(.33, 1, .68, 1);
|
||||
//--layer-transition: none;
|
||||
--btn-corner-transition: transform .2s cubic-bezier(.34, 1.56, .64, 1);
|
||||
--message-handhelds-margin: 5.5625rem;
|
||||
--message-beside-button-margin: 2.875rem;
|
||||
--message-time-background: rgba(0, 0, 0, .35);
|
||||
@ -1104,6 +1104,34 @@ img.emoji {
|
||||
color: #707579;
|
||||
}
|
||||
|
||||
@keyframes grow-icon {
|
||||
0% {
|
||||
transform: scale(.5);
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes hide-icon {
|
||||
from {
|
||||
transform: scale(1);
|
||||
opacity: .4;
|
||||
}
|
||||
|
||||
to {
|
||||
transform: scale(.5);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// *:not(input):not(textarea) {
|
||||
// -webkit-user-select: none; /* disable selection/Copy of UIWebView */
|
||||
// -webkit-touch-callout: none; /* disable the IOS popup when long-press on a link */
|
||||
|
Loading…
x
Reference in New Issue
Block a user