Browse Source

Ripple changes

Chat scroll fix
Date format in chat list fix
Animated send button
Chat input is now lockable
master
morethanwords 4 years ago
parent
commit
735721fee8
  1. 108
      src/components/chat/input.ts
  2. 29
      src/components/ripple.ts
  3. 2
      src/components/wrappers.ts
  4. 32
      src/helpers/date.ts
  5. 5
      src/helpers/mediaSizes.ts
  6. 17
      src/index.hbs
  7. 31
      src/lib/appManagers/appDialogsManager.ts
  8. 61
      src/lib/appManagers/appImManager.ts
  9. 17
      src/lib/appManagers/appSidebarRight.ts
  10. 2
      src/lib/appManagers/appWebPagesManager.ts
  11. 106
      src/scss/partials/_chat.scss
  12. 21
      src/scss/partials/_ripple.scss
  13. 1
      src/scss/partials/popups/_popup.scss
  14. 30
      src/scss/style.scss

108
src/components/chat/input.ts

@ -14,6 +14,7 @@ import { touchSupport } from "../../lib/config"; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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();

29
src/components/ripple.ts

@ -1,3 +1,4 @@ @@ -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 @@ -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 @@ -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 @@ -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});

2
src/components/wrappers.ts

@ -20,6 +20,7 @@ import { PhotoSize } from '../layer'; @@ -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 @@ -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

@ -0,0 +1,32 @@ @@ -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;
};

5
src/helpers/mediaSizes.ts

@ -80,7 +80,7 @@ class MediaSizes extends EventListenerBase<{ @@ -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<{ @@ -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;

17
src/index.hbs

@ -452,11 +452,11 @@ @@ -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 @@ @@ -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 @@ @@ -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;">

31
src/lib/appManagers/appDialogsManager.ts

@ -17,6 +17,7 @@ import { touchSupport } from "../config"; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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) {

61
src/lib/appManagers/appImManager.ts

@ -40,7 +40,7 @@ import { ChatAudio } from '../../components/chat/audio'; @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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 { @@ -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));
//this.log('performHistoryResult: will render some messages:', history.length);
let realLength = this.scrollable.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 { @@ -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 { @@ -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);

17
src/lib/appManagers/appSidebarRight.ts

@ -80,7 +80,7 @@ export class AppSidebarRight extends SidebarSlider { @@ -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 { @@ -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 { @@ -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 { @@ -163,7 +154,7 @@ export class AppSidebarRight extends SidebarSlider {
set();
setTimeout(resolve, 200);
}
});
}); */
}
}

2
src/lib/appManagers/appWebPagesManager.ts

@ -101,6 +101,8 @@ class AppWebPagesManager { @@ -101,6 +101,8 @@ class AppWebPagesManager {
msgs: msgs
});
}
return apiWebPage;
}
public getWebPage(id: string) {

106
src/scss/partials/_chat.scss

@ -298,9 +298,19 @@ @@ -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 @@ @@ -345,6 +355,14 @@
left: -124px;
}
}
&.is-locked {
pointer-events: none;
.btn-icon {
color: #c6cbce;
}
}
&.is-recording {
#btn-record-cancel {
@ -354,6 +372,11 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 @@ @@ -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 {

21
src/scss/partials/_ripple.scss

@ -50,17 +50,17 @@ @@ -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 @@ @@ -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);

1
src/scss/partials/popups/_popup.scss

@ -99,6 +99,7 @@ @@ -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);

30
src/scss/style.scss

@ -54,9 +54,9 @@ $large-screen: 1680px; @@ -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 { @@ -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…
Cancel
Save