Browse Source

Multiselect: forward and delete

Chat input helper is now clickable
Popup stickers lower resolution for handhelds
master
morethanwords 4 years ago
parent
commit
baf1f78618
  1. 16
      src/components/appSelectPeers.ts
  2. 86
      src/components/chat/contextMenu.ts
  3. 56
      src/components/chat/input.ts
  4. 76
      src/components/chat/selection.ts
  5. 15
      src/components/popup.ts
  6. 65
      src/components/popupDeleteMessages.ts
  7. 11
      src/components/popupForward.ts
  8. 18
      src/components/popupStickers.ts
  9. 4
      src/components/wrappers.ts
  10. 2
      src/helpers/touchSupport.ts
  11. 6
      src/index.hbs
  12. 60
      src/lib/appManagers/appImManager.ts
  13. 15
      src/lib/appManagers/appMessagesManager.ts
  14. 10
      src/lib/richtextprocessor.ts
  15. 11
      src/lib/utils.ts
  16. 8
      src/scss/components/_global.scss
  17. 192
      src/scss/partials/_chat.scss
  18. 34
      src/scss/partials/_leftSidebar.scss
  19. 4
      src/scss/partials/_selector.scss
  20. 6
      src/scss/partials/popups/_forward.scss
  21. 8
      src/scss/partials/popups/_stickers.scss
  22. 22
      src/scss/style.scss

16
src/components/appSelectPeers.ts

@ -36,13 +36,22 @@ export default class AppSelectPeers {
private cachedContacts: number[]; private cachedContacts: number[];
private loadedWhat: Partial<{[k in 'dialogs' | 'archived' | 'contacts']: true}> = {}; private loadedWhat: Partial<{[k in 'dialogs' | 'archived' | 'contacts']: true}> = {};
private renderedPeerIDs: Set<number> = new Set();
constructor(private appendTo: HTMLElement, private onChange?: (length: number) => void, private peerType: PeerType[] = ['dialogs'], onFirstRender?: () => void, private renderResultsFunc?: (peerIDs: number[]) => void, private chatRightsAction?: ChatRights, private multiSelect = true) { constructor(private appendTo: HTMLElement, private onChange?: (length: number) => void, private peerType: PeerType[] = ['dialogs'], onFirstRender?: () => void, private renderResultsFunc?: (peerIDs: number[]) => void, private chatRightsAction?: ChatRights, private multiSelect = true) {
this.container.classList.add('selector'); this.container.classList.add('selector');
if(!this.renderResultsFunc) { const f = (renderResultsFunc || this.renderResults).bind(this);
this.renderResultsFunc = this.renderResults; this.renderResultsFunc = (peerIDs: number[]) => {
} peerIDs = peerIDs.filter(peerID => {
const notRendered = !this.renderedPeerIDs.has(peerID);
if(notRendered) this.renderedPeerIDs.add(peerID);
return notRendered;
});
return f(peerIDs);
};
this.input = document.createElement('input'); this.input = document.createElement('input');
this.input.classList.add('selector-search-input'); this.input.classList.add('selector-search-input');
@ -130,6 +139,7 @@ export default class AppSelectPeers {
this.promise = null; this.promise = null;
this.list.innerHTML = ''; this.list.innerHTML = '';
this.query = value; this.query = value;
this.renderedPeerIDs.clear();
//console.log('selectPeers input:', this.query); //console.log('selectPeers input:', this.query);
this.getMoreResults(); this.getMoreResults();

86
src/components/chat/contextMenu.ts

@ -1,13 +1,15 @@
import { isTouchSupported } from "../../helpers/touchSupport";
import appChatsManager from "../../lib/appManagers/appChatsManager"; import appChatsManager from "../../lib/appManagers/appChatsManager";
import appImManager from "../../lib/appManagers/appImManager"; import appImManager from "../../lib/appManagers/appImManager";
import appMessagesManager from "../../lib/appManagers/appMessagesManager"; import appMessagesManager from "../../lib/appManagers/appMessagesManager";
import appPeersManager from "../../lib/appManagers/appPeersManager"; import appPeersManager from "../../lib/appManagers/appPeersManager";
import appPollsManager, { Poll } from "../../lib/appManagers/appPollsManager"; import appPollsManager, { Poll } from "../../lib/appManagers/appPollsManager";
import $rootScope from "../../lib/rootScope"; import $rootScope from "../../lib/rootScope";
import { findUpClassName } from "../../lib/utils"; import { cancelEvent, cancelSelection, findUpClassName } from "../../lib/utils";
import ButtonMenu, { ButtonMenuItemOptions } from "../buttonMenu"; import ButtonMenu, { ButtonMenuItemOptions } from "../buttonMenu";
import { attachContextMenuListener, openBtnMenu, positionMenu } from "../misc"; import { attachContextMenuListener, openBtnMenu, positionMenu } from "../misc";
import { PopupButton } from "../popup"; import { PopupButton } from "../popup";
import PopupDeleteMessages from "../popupDeleteMessages";
import PopupForward from "../popupForward"; import PopupForward from "../popupForward";
import PopupPeer from "../popupPeer"; import PopupPeer from "../popupPeer";
import appSidebarRight from "../sidebarRight"; import appSidebarRight from "../sidebarRight";
@ -21,7 +23,7 @@ export default class ChatContextMenu {
public msgID: number; public msgID: number;
constructor(private attachTo: HTMLElement) { constructor(private attachTo: HTMLElement) {
attachContextMenuListener(attachTo, (e) => { const onContextMenu = (e: MouseEvent | Touch) => {
if(this.init) { if(this.init) {
this.init(); this.init();
this.init = null; this.init = null;
@ -69,7 +71,29 @@ export default class ChatContextMenu {
}); });
/////this.log('contextmenu', e, bubble, msgID, side); /////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 className = (e.target as HTMLElement).className;
const good = ['bubble', 'bubble__container', 'message', 'time', 'inner'].find(c => className.includes(c));
if(good) {
onContextMenu(e);
}
});
attachContextMenuListener(attachTo, (e) => {
if(appImManager.chatSelection.isSelecting) return;
cancelSelection();
cancelEvent(e as any);
let bubble = findUpClassName(e.target, 'bubble');
if(bubble) {
appImManager.chatSelection.toggleByBubble(bubble);
}
});
} else attachContextMenuListener(attachTo, onContextMenu);
} }
private init = () => { private init = () => {
@ -145,7 +169,7 @@ export default class ChatContextMenu {
icon: 'delete danger', icon: 'delete danger',
text: 'Delete', text: 'Delete',
onClick: this.onDeleteClick, onClick: this.onDeleteClick,
verify: () => this.peerID > 0 || appMessagesManager.getMessage(this.msgID).fromID == $rootScope.myID || appChatsManager.hasRights(-this.peerID, 'deleteRevoke') verify: () => appMessagesManager.canDeleteMessage(this.msgID)
}]; }];
this.element = ButtonMenu(this.buttons); this.element = ButtonMenu(this.buttons);
@ -217,58 +241,6 @@ export default class ChatContextMenu {
}; };
private onDeleteClick = () => { private onDeleteClick = () => {
const peerID = $rootScope.selectedPeerID; new PopupDeleteMessages([this.msgID]);
const firstName = appPeersManager.getPeerTitle(peerID, false, true);
const msgID = this.msgID;
const callback = (revoke: boolean) => {
appMessagesManager.deleteMessages([msgID], revoke);
};
let title: string, description: string, buttons: PopupButton[];
title = 'Delete Message?';
description = `Are you sure you want to delete this message?`;
if(peerID == $rootScope.myID) {
buttons = [{
text: 'DELETE',
isDanger: true,
callback: () => callback(false)
}];
} else {
buttons = [{
text: 'DELETE JUST FOR ME',
isDanger: true,
callback: () => callback(false)
}];
if(peerID > 0) {
buttons.push({
text: 'DELETE FOR ME AND ' + firstName,
isDanger: true,
callback: () => callback(true)
});
} else if(appChatsManager.hasRights(-peerID, 'deleteRevoke')) {
buttons.push({
text: 'DELETE FOR ALL',
isDanger: true,
callback: () => callback(true)
});
}
}
buttons.push({
text: 'CANCEL',
isCancel: true
});
const popup = new PopupPeer('popup-delete-chat', {
peerID: peerID,
title: title,
description: description,
buttons: buttons
});
popup.show();
}; };
} }

56
src/components/chat/input.ts

@ -11,10 +11,11 @@ import apiManager from "../../lib/mtproto/mtprotoworker";
import opusDecodeController from "../../lib/opusDecodeController"; import opusDecodeController from "../../lib/opusDecodeController";
import { RichTextProcessor } from "../../lib/richtextprocessor"; import { RichTextProcessor } from "../../lib/richtextprocessor";
import $rootScope from '../../lib/rootScope'; import $rootScope from '../../lib/rootScope';
import { cancelEvent, getRichValue } from "../../lib/utils"; import { cancelEvent, findUpClassName, getRichValue } from "../../lib/utils";
import ButtonMenu, { ButtonMenuItemOptions } from '../buttonMenu'; import ButtonMenu, { ButtonMenuItemOptions } from '../buttonMenu';
import emoticonsDropdown from "../emoticonsDropdown"; import emoticonsDropdown from "../emoticonsDropdown";
import PopupCreatePoll from "../popupCreatePoll"; import PopupCreatePoll from "../popupCreatePoll";
import PopupForward from '../popupForward';
import PopupNewMedia from '../popupNewMedia'; import PopupNewMedia from '../popupNewMedia';
import { ripple } from '../ripple'; import { ripple } from '../ripple';
import Scrollable from "../scrollable"; import Scrollable from "../scrollable";
@ -205,7 +206,7 @@ export class ChatInput {
if(this.lastUrl != url) return; if(this.lastUrl != url) return;
//console.log('got webpage: ', webpage); //console.log('got webpage: ', webpage);
this.setTopInfo('webpage', () => {}, webpage.site_name || webpage.title, webpage.description || webpage.url); this.setTopInfo('webpage', () => {}, webpage.site_name || webpage.title || 'Webpage', webpage.description || webpage.url || '');
delete this.noWebPage; delete this.noWebPage;
this.willSendWebPage = webpage; this.willSendWebPage = webpage;
@ -267,7 +268,7 @@ export class ChatInput {
//console.log('messageInput paste', text, entities); //console.log('messageInput paste', text, entities);
entities = entities.filter(e => e._ == 'messageEntityEmoji' || e._ == 'messageEntityLinebreak'); entities = entities.filter(e => e._ == 'messageEntityEmoji' || e._ == 'messageEntityLinebreak');
//text = RichTextProcessor.wrapEmojiText(text); //text = RichTextProcessor.wrapEmojiText(text);
text = RichTextProcessor.wrapRichText(text, {entities}); text = RichTextProcessor.wrapRichText(text, {entities, noLinks: true});
// console.log('messageInput paste after', text); // console.log('messageInput paste after', text);
@ -453,7 +454,7 @@ export class ChatInput {
return; */ return; */
let perf = performance.now(); //let perf = performance.now();
opusDecodeController.decode(typedArray, true).then(result => { opusDecodeController.decode(typedArray, true).then(result => {
//console.log('WAVEFORM!:', /* waveform, */performance.now() - perf); //console.log('WAVEFORM!:', /* waveform, */performance.now() - perf);
@ -509,6 +510,33 @@ export class ChatInput {
this.clearHelper(); this.clearHelper();
this.updateSendBtn(); this.updateSendBtn();
}); });
let d = false;
this.replyElements.container.addEventListener('click', (e) => {
if(!findUpClassName(e.target, 'reply-wrapper')) return;
if(this.helperType == 'forward') {
if(d) return;
d = true;
const mids = this.forwardingMids.slice();
const helperFunc = this.helperFunc;
this.clearHelper();
let selected = false;
new PopupForward(mids, () => {
selected = true;
}, () => {
d = false;
if(!selected) {
helperFunc();
}
});
} else if(this.helperType == 'reply') {
appImManager.setPeer($rootScope.selectedPeerID, this.replyToMsgID);
} else if(this.helperType == 'edit') {
appImManager.setPeer($rootScope.selectedPeerID, this.editMsgID);
}
});
} }
private isInputEmpty() { private isInputEmpty() {
@ -579,8 +607,12 @@ export class ChatInput {
}); });
} }
// * wait for sendText set messageID for invokeAfterMsg
if(this.forwardingMids.length) { if(this.forwardingMids.length) {
appMessagesManager.forwardMessages(appImManager.peerID, this.forwardingMids); const mids = this.forwardingMids.slice();
setTimeout(() => {
appMessagesManager.forwardMessages(appImManager.peerID, mids);
}, 0);
} }
this.onMessageSent(); this.onMessageSent();
@ -629,7 +661,7 @@ export class ChatInput {
this.setTopInfo('forward', f, title, mids.length + ' forwarded messages'); this.setTopInfo('forward', f, title, mids.length + ' forwarded messages');
} }
this.forwardingMids = mids; this.forwardingMids = mids.slice();
}; };
f(); f();
@ -640,6 +672,12 @@ export class ChatInput {
this.messageInput.innerText = ''; this.messageInput.innerText = '';
} }
if(type) {
this.lastUrl = '';
delete this.noWebPage;
this.willSendWebPage = null;
}
this.replyToMsgID = 0; this.replyToMsgID = 0;
this.forwardingMids.length = 0; this.forwardingMids.length = 0;
this.editMsgID = 0; this.editMsgID = 0;
@ -660,9 +698,13 @@ export class ChatInput {
} }
this.chatInput.parentElement.classList.add('is-helper-active'); this.chatInput.parentElement.classList.add('is-helper-active');
/* const scroll = appImManager.scrollable;
if(scroll.isScrolledDown && !scroll.scrollLocked && !appImManager.messagesQueuePromise && !appImManager.setPeerPromise) {
scroll.scrollTo(scroll.scrollHeight, 'top', true, true, 200);
} */
if(input !== undefined) { if(input !== undefined) {
this.messageInput.innerHTML = input ? RichTextProcessor.wrapRichText(input) : ''; this.messageInput.innerHTML = input ? RichTextProcessor.wrapRichText(input, {noLinks: true}) : '';
} }
setTimeout(() => { setTimeout(() => {

76
src/components/chat/selection.ts

@ -1,10 +1,11 @@
import { isTouchSupported } from "../../helpers/touchSupport"; import { isTouchSupported } from "../../helpers/touchSupport";
import type { AppImManager } from "../../lib/appManagers/appImManager"; import type { AppImManager } from "../../lib/appManagers/appImManager";
import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager"; import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager";
import { cancelEvent, cancelSelection, findUpClassName } from "../../lib/utils"; import { cancelEvent, cancelSelection, findUpClassName, getSelectedText } from "../../lib/utils";
import Button from "../button"; import Button from "../button";
import ButtonIcon from "../buttonIcon"; import ButtonIcon from "../buttonIcon";
import CheckboxField from "../checkbox"; import CheckboxField from "../checkbox";
import PopupDeleteMessages from "../popupDeleteMessages";
import PopupForward from "../popupForward"; import PopupForward from "../popupForward";
import { toast } from "../toast"; import { toast } from "../toast";
@ -34,7 +35,7 @@ const SetTransition = (element: HTMLElement, className: string, forwards: boolea
}; };
const MAX_SELECTION_LENGTH = 100; const MAX_SELECTION_LENGTH = 100;
const MIN_CLICK_MOVE = 32; // minimum bubble height //const MIN_CLICK_MOVE = 32; // minimum bubble height
export default class ChatSelection { export default class ChatSelection {
public selectedMids: Set<number> = new Set(); public selectedMids: Set<number> = new Set();
@ -42,14 +43,25 @@ export default class ChatSelection {
private selectionContainer: HTMLElement; private selectionContainer: HTMLElement;
private selectionCountEl: HTMLElement; private selectionCountEl: HTMLElement;
private selectionForwardBtn: HTMLElement;
private selectionDeleteBtn: HTMLElement;
constructor(private appImManager: AppImManager, private appMessagesManager: AppMessagesManager) { public selectedText: string;
if(isTouchSupported) return;
constructor(private appImManager: AppImManager, private appMessagesManager: AppMessagesManager) {
const bubblesContainer = appImManager.bubblesContainer; const bubblesContainer = appImManager.bubblesContainer;
if(isTouchSupported) {
bubblesContainer.addEventListener('touchend', (e) => {
if(!this.isSelecting) return;
this.selectedText = getSelectedText();
});
return;
}
bubblesContainer.addEventListener('mousedown', (e) => { bubblesContainer.addEventListener('mousedown', (e) => {
//console.log('selection mousedown', e); //console.log('selection mousedown', e);
if(e.button != 0) { // LEFT BUTTON if(e.button != 0 || (!this.selectedMids.size && !(e.target as HTMLElement).classList.contains('bubble'))) { // LEFT BUTTON
return; return;
} }
@ -169,6 +181,28 @@ export default class ChatSelection {
if(!this.selectedMids.size) return; if(!this.selectedMids.size) return;
this.selectionCountEl.innerText = this.selectedMids.size + ' Message' + (this.selectedMids.size == 1 ? '' : 's'); this.selectionCountEl.innerText = this.selectedMids.size + ' Message' + (this.selectedMids.size == 1 ? '' : 's');
let cantForward = false, cantDelete = false;
for(const mid of this.selectedMids.values()) {
const message = this.appMessagesManager.getMessage(mid);
if(!cantForward) {
if(message.action) {
cantForward = true;
}
}
if(!cantDelete) {
const canDelete = this.appMessagesManager.canDeleteMessage(mid);
if(!canDelete) {
cantDelete = true;
}
}
if(cantForward && cantDelete) break;
}
this.selectionForwardBtn.toggleAttribute('disabled', cantForward);
this.selectionDeleteBtn.toggleAttribute('disabled', cantDelete);
} }
public toggleSelection(toggleCheckboxes = true) { public toggleSelection(toggleCheckboxes = true) {
@ -180,11 +214,23 @@ export default class ChatSelection {
const bubblesContainer = this.appImManager.bubblesContainer; const bubblesContainer = this.appImManager.bubblesContainer;
//bubblesContainer.classList.toggle('is-selecting', !!this.selectedMids.size); //bubblesContainer.classList.toggle('is-selecting', !!this.selectedMids.size);
/* if(bubblesContainer.classList.contains('is-chat-input-hidden')) {
const scrollable = this.appImManager.scrollable;
if(scrollable.isScrolledDown) {
scrollable.scrollTo(scrollable.scrollHeight, 'top', true, true, 200);
}
} */
SetTransition(bubblesContainer, 'is-selecting', !!this.selectedMids.size, 200, () => { SetTransition(bubblesContainer, 'is-selecting', !!this.selectedMids.size, 200, () => {
if(!this.isSelecting) { if(!this.isSelecting) {
this.selectionContainer.remove(); this.selectionContainer.remove();
this.selectionContainer = null; this.selectionContainer = this.selectionForwardBtn = this.selectionDeleteBtn = null;
this.selectedText = undefined;
} }
window.requestAnimationFrame(() => {
this.appImManager.onScroll();
});
}); });
//const chatInput = this.appImManager.chatInput; //const chatInput = this.appImManager.chatInput;
@ -201,19 +247,23 @@ export default class ChatSelection {
this.selectionCountEl = document.createElement('div'); this.selectionCountEl = document.createElement('div');
this.selectionCountEl.classList.add('selection-container-count'); this.selectionCountEl.classList.add('selection-container-count');
const btnForward = Button('btn-primary btn-transparent', {icon: 'forward'}); this.selectionForwardBtn = Button('btn-primary btn-transparent selection-container-forward', {icon: 'forward'});
btnForward.append('Forward'); this.selectionForwardBtn.append('Forward');
this.selectionForwardBtn.addEventListener('click', () => {
btnForward.addEventListener('click', () => {
new PopupForward([...this.selectedMids], () => { new PopupForward([...this.selectedMids], () => {
this.cancelSelection(); this.cancelSelection();
}); });
}); });
const btnDelete = Button('btn-primary btn-transparent danger', {icon: 'delete'}); this.selectionDeleteBtn = Button('btn-primary btn-transparent danger selection-container-delete', {icon: 'delete'});
btnDelete.append('Delete'); this.selectionDeleteBtn.append('Delete');
this.selectionDeleteBtn.addEventListener('click', () => {
new PopupDeleteMessages([...this.selectedMids], () => {
this.cancelSelection();
});
});
this.selectionContainer.append(btnCancel, this.selectionCountEl, btnForward, btnDelete); this.selectionContainer.append(btnCancel, this.selectionCountEl, this.selectionForwardBtn, this.selectionDeleteBtn);
inputMessageDiv.append(this.selectionContainer); inputMessageDiv.append(this.selectionContainer);
} }

15
src/components/popup.ts

@ -1,6 +1,5 @@
import $rootScope from "../lib/rootScope"; import $rootScope from "../lib/rootScope";
import { cancelEvent } from "../lib/utils"; import { cancelEvent, findUpClassName } from "../lib/utils";
import AvatarElement from "./avatar";
import { ripple } from "./ripple"; import { ripple } from "./ripple";
export class PopupElement { export class PopupElement {
@ -16,7 +15,7 @@ export class PopupElement {
protected onCloseAfterTimeout: () => void; protected onCloseAfterTimeout: () => void;
protected onEscape: () => boolean = () => true; protected onEscape: () => boolean = () => true;
constructor(className: string, buttons?: Array<PopupButton>, options: Partial<{closable: boolean, withConfirm: string, body: boolean}> = {}) { constructor(className: string, buttons?: Array<PopupButton>, options: Partial<{closable: true, overlayClosable: true, withConfirm: string, body: true}> = {}) {
this.element.classList.add('popup'); this.element.classList.add('popup');
this.element.className = 'popup' + (className ? ' ' + className : ''); this.element.className = 'popup' + (className ? ' ' + className : '');
this.container.classList.add('popup-container', 'z-depth-1'); this.container.classList.add('popup-container', 'z-depth-1');
@ -33,6 +32,16 @@ export class PopupElement {
this.header.prepend(this.closeBtn); this.header.prepend(this.closeBtn);
this.closeBtn.addEventListener('click', this.destroy, {once: true}); this.closeBtn.addEventListener('click', this.destroy, {once: true});
if(options.overlayClosable) {
const onOverlayClick = (e: MouseEvent) => {
if(!findUpClassName(e.target, 'popup-container')) {
this.closeBtn.click();
}
};
this.element.addEventListener('click', onOverlayClick, {once: true});
}
} }
window.addEventListener('keydown', this._onKeyDown, {capture: true}); window.addEventListener('keydown', this._onKeyDown, {capture: true});

65
src/components/popupDeleteMessages.ts

@ -0,0 +1,65 @@
import appChatsManager from "../lib/appManagers/appChatsManager";
import appMessagesManager from "../lib/appManagers/appMessagesManager";
import appPeersManager from "../lib/appManagers/appPeersManager";
import $rootScope from "../lib/rootScope";
import { PopupButton } from "./popup";
import PopupPeer from "./popupPeer";
export default class PopupDeleteMessages {
constructor(mids: number[], onConfirm?: () => void) {
const peerID = $rootScope.selectedPeerID;
const firstName = appPeersManager.getPeerTitle(peerID, false, true);
mids = mids.slice();
const callback = (revoke: boolean) => {
onConfirm && onConfirm();
appMessagesManager.deleteMessages(mids, revoke);
};
let title: string, description: string, buttons: PopupButton[];
title = `Delete Message${mids.length == 1 ? '' : 's'}?`;
description = `Are you sure you want to delete ${mids.length == 1 ? 'this message' : 'these messages'}?`;
if(peerID == $rootScope.myID) {
buttons = [{
text: 'DELETE',
isDanger: true,
callback: () => callback(false)
}];
} else {
buttons = [{
text: 'DELETE JUST FOR ME',
isDanger: true,
callback: () => callback(false)
}];
if(peerID > 0) {
buttons.push({
text: 'DELETE FOR ME AND ' + firstName,
isDanger: true,
callback: () => callback(true)
});
} else if(appChatsManager.hasRights(-peerID, 'deleteRevoke')) {
buttons.push({
text: 'DELETE FOR ALL',
isDanger: true,
callback: () => callback(true)
});
}
}
buttons.push({
text: 'CANCEL',
isCancel: true
});
const popup = new PopupPeer('popup-delete-chat', {
peerID: peerID,
title: title,
description: description,
buttons: buttons
});
popup.show();
}
}

11
src/components/popupForward.ts

@ -1,3 +1,4 @@
import { isTouchSupported } from "../helpers/touchSupport";
import appImManager from "../lib/appManagers/appImManager"; import appImManager from "../lib/appManagers/appImManager";
import AppSelectPeers from "./appSelectPeers"; import AppSelectPeers from "./appSelectPeers";
import { PopupElement } from "./popup"; import { PopupElement } from "./popup";
@ -6,8 +7,10 @@ export default class PopupForward extends PopupElement {
private selector: AppSelectPeers; private selector: AppSelectPeers;
//private scrollable: Scrollable; //private scrollable: Scrollable;
constructor(mids: number[], onSelect?: () => Promise<void> | void) { constructor(mids: number[], onSelect?: () => Promise<void> | void, onClose?: () => void) {
super('popup-forward', null, {closable: true, body: true}); super('popup-forward', null, {closable: true, overlayClosable: true, body: true});
if(onClose) this.onClose = onClose;
this.selector = new AppSelectPeers(this.body, async() => { this.selector = new AppSelectPeers(this.body, async() => {
const peerID = this.selector.getSelected()[0]; const peerID = this.selector.getSelected()[0];
@ -21,6 +24,10 @@ export default class PopupForward extends PopupElement {
appImManager.chatInputC.initMessagesForward(mids.slice()); appImManager.chatInputC.initMessagesForward(mids.slice());
}, ['dialogs', 'contacts'], () => { }, ['dialogs', 'contacts'], () => {
this.show(); this.show();
if(!isTouchSupported) {
this.selector.input.focus();
}
}, null, 'send', false); }, null, 'send', false);
//this.scrollable = new Scrollable(this.body); //this.scrollable = new Scrollable(this.body);

18
src/components/popupStickers.ts

@ -9,6 +9,7 @@ import animationIntersector from "./animationIntersector";
import { findUpClassName } from "../lib/utils"; import { findUpClassName } from "../lib/utils";
import appImManager from "../lib/appManagers/appImManager"; import appImManager from "../lib/appManagers/appImManager";
import { StickerSet } from "../layer"; import { StickerSet } from "../layer";
import mediaSizes from "../helpers/mediaSizes";
const ANIMATION_GROUP = 'STICKERS-POPUP'; const ANIMATION_GROUP = 'STICKERS-POPUP';
@ -24,7 +25,7 @@ export default class PopupStickers extends PopupElement {
id: string, id: string,
access_hash: string access_hash: string
}) { }) {
super('popup-stickers', null, {closable: true, body: true}); super('popup-stickers', null, {closable: true, overlayClosable: true, body: true});
this.h6 = document.createElement('h6'); this.h6 = document.createElement('h6');
this.h6.innerText = 'Loading...'; this.h6.innerText = 'Loading...';
@ -36,21 +37,12 @@ export default class PopupStickers extends PopupElement {
animationIntersector.checkAnimations(false); animationIntersector.checkAnimations(false);
this.stickersFooter.removeEventListener('click', this.onFooterClick); this.stickersFooter.removeEventListener('click', this.onFooterClick);
this.stickersDiv.removeEventListener('click', this.onStickersClick); this.stickersDiv.removeEventListener('click', this.onStickersClick);
this.element.removeEventListener('click', onOverlayClick);
}; };
this.onCloseAfterTimeout = () => { this.onCloseAfterTimeout = () => {
animationIntersector.checkAnimations(undefined, ANIMATION_GROUP); animationIntersector.checkAnimations(undefined, ANIMATION_GROUP);
}; };
const onOverlayClick = (e: MouseEvent) => {
if(!findUpClassName(e.target, 'popup-container')) {
this.closeBtn.click();
}
};
this.element.addEventListener('click', onOverlayClick);
const div = document.createElement('div'); const div = document.createElement('div');
div.classList.add('sticker-set'); div.classList.add('sticker-set');
@ -129,6 +121,8 @@ export default class PopupStickers extends PopupElement {
const div = document.createElement('div'); const div = document.createElement('div');
div.classList.add('sticker-set-sticker'); div.classList.add('sticker-set-sticker');
const size = mediaSizes.active.esgSticker.width;
wrapSticker({ wrapSticker({
doc, doc,
@ -137,8 +131,8 @@ export default class PopupStickers extends PopupElement {
group: ANIMATION_GROUP, group: ANIMATION_GROUP,
play: true, play: true,
loop: true, loop: true,
width: 80, width: size,
height: 80 height: size
}); });
this.stickersDiv.append(div); this.stickersDiv.append(div);

4
src/components/wrappers.ts

@ -10,7 +10,7 @@ import appMessagesManager from '../lib/appManagers/appMessagesManager';
import appPhotosManager, { MyPhoto } from '../lib/appManagers/appPhotosManager'; import appPhotosManager, { MyPhoto } from '../lib/appManagers/appPhotosManager';
import LottieLoader from '../lib/lottieLoader'; import LottieLoader from '../lib/lottieLoader';
import VideoPlayer from '../lib/mediaPlayer'; import VideoPlayer from '../lib/mediaPlayer';
import { formatBytes, getEmojiToneIndex, isInDOM } from "../lib/utils"; import { cancelEvent, formatBytes, getEmojiToneIndex, isInDOM } from "../lib/utils";
import webpWorkerController from '../lib/webp/webpWorkerController'; import webpWorkerController from '../lib/webp/webpWorkerController';
import animationIntersector from './animationIntersector'; import animationIntersector from './animationIntersector';
import appMediaPlaybackController from './appMediaPlaybackController'; import appMediaPlaybackController from './appMediaPlaybackController';
@ -353,7 +353,7 @@ export function wrapDocument(doc: MyDocument, withTime = false, uploading = fals
let preloader: ProgressivePreloader; let preloader: ProgressivePreloader;
let download: DownloadBlob; let download: DownloadBlob;
docDiv.addEventListener('click', () => { docDiv.addEventListener('click', (e) => {
if(!download) { if(!download) {
if(downloadDiv.classList.contains('downloading')) { if(downloadDiv.classList.contains('downloading')) {
return; // means not ready yet return; // means not ready yet

2
src/helpers/touchSupport.ts

@ -1,2 +1,2 @@
// @ts-ignore // @ts-ignore
export const isTouchSupported = ('ontouchstart' in window) || (window.DocumentTouch && document instanceof DocumentTouch); export const isTouchSupported = ('ontouchstart' in window) || (window.DocumentTouch && document instanceof DocumentTouch)/* || true */;

6
src/index.hbs

@ -440,8 +440,10 @@
</div> </div>
</div> </div>
<div id="bubbles" class="scrolled-down"> <div id="bubbles" class="scrolled-down">
<div id="bubbles-inner"></div> {{!-- <div id="bubbles-transform-helper"> --}}
<div id="bubbles-go-down" class="tgico-down btn-corner z-depth-1 rp hide"></div> <div id="bubbles-inner"></div>
<div id="bubbles-go-down" class="tgico-down btn-corner z-depth-1 rp hide"></div>
{{!-- </div> --}}
</div> </div>
<div id="chat-input" style="display: none;"> <div id="chat-input" style="display: none;">
<div class="chat-input-container"> <div class="chat-input-container">

60
src/lib/appManagers/appImManager.ts

@ -63,6 +63,7 @@ const ANIMATION_GROUP = 'chat';
export class AppImManager { export class AppImManager {
public columnEl = document.getElementById('column-center') as HTMLDivElement; public columnEl = document.getElementById('column-center') as HTMLDivElement;
public backgroundEl = this.columnEl.firstElementChild as HTMLDivElement;
public btnJoin = this.columnEl.querySelector('.chat-join') as HTMLButtonElement; public btnJoin = this.columnEl.querySelector('.chat-join') as HTMLButtonElement;
public btnMute = this.columnEl.querySelector('.chat-mute-button') as HTMLButtonElement; public btnMute = this.columnEl.querySelector('.chat-mute-button') as HTMLButtonElement;
public avatarEl = document.getElementById('im-avatar') as AvatarElement; public avatarEl = document.getElementById('im-avatar') as AvatarElement;
@ -123,7 +124,7 @@ export class AppImManager {
public contextMenu = new ChatContextMenu(this.bubblesContainer); public contextMenu = new ChatContextMenu(this.bubblesContainer);
private setPeerPromise: Promise<boolean> = null; public setPeerPromise: Promise<boolean> = null;
public bubbleGroups = new BubbleGroups(); public bubbleGroups = new BubbleGroups();
@ -137,7 +138,7 @@ export class AppImManager {
private loadedTopTimes = 0; private loadedTopTimes = 0;
private loadedBottomTimes = 0; private loadedBottomTimes = 0;
private messagesQueuePromise: Promise<void> = null; public messagesQueuePromise: Promise<void> = null;
private messagesQueue: {message: any, bubble: HTMLDivElement, reverse: boolean, promises: Promise<void>[]}[] = []; private messagesQueue: {message: any, bubble: HTMLDivElement, reverse: boolean, promises: Promise<void>[]}[] = [];
private messagesQueueOnRender: () => void = null; private messagesQueueOnRender: () => void = null;
@ -437,9 +438,19 @@ export class AppImManager {
} }
// ! Trusted - due to audio autoclick // ! Trusted - due to audio autoclick
if(this.chatSelection.isSelecting && !bubble.classList.contains('service') && e.isTrusted) { if(this.chatSelection.isSelecting && e.isTrusted) {
if(bubble.classList.contains('service') && bubble.dataset.mid === undefined) {
return;
}
cancelEvent(e); cancelEvent(e);
//console.log('bubble click', e); //console.log('bubble click', e);
if(isTouchSupported && this.chatSelection.selectedText) {
this.chatSelection.selectedText = undefined;
return;
}
this.chatSelection.toggleByBubble(bubble); this.chatSelection.toggleByBubble(bubble);
return; return;
} }
@ -526,6 +537,7 @@ export class AppImManager {
new AppMediaViewer().openMedia(message, targets[idx].element, true, new AppMediaViewer().openMedia(message, targets[idx].element, true,
targets.slice(0, idx), targets.slice(idx + 1)/* , !message.grouped_id */); targets.slice(0, idx), targets.slice(idx + 1)/* , !message.grouped_id */);
cancelEvent(e);
//appMediaViewer.openMedia(message, target as HTMLImageElement); //appMediaViewer.openMedia(message, target as HTMLImageElement);
return; return;
} }
@ -552,7 +564,7 @@ export class AppImManager {
if(!isNaN(peerID)) { if(!isNaN(peerID)) {
this.setPeer(peerID); this.setPeer(peerID);
} }
return; return;
} else if(target.tagName == "AVATAR-ELEMENT") { } else if(target.tagName == "AVATAR-ELEMENT") {
let peerID = +target.getAttribute('peer'); let peerID = +target.getAttribute('peer');
@ -560,7 +572,7 @@ export class AppImManager {
if(!isNaN(peerID)) { if(!isNaN(peerID)) {
this.setPeer(peerID); this.setPeer(peerID);
} }
return; return;
} }
@ -583,7 +595,7 @@ export class AppImManager {
} }
//console.log('chatInner click', e); //console.log('chatInner click', e);
}); }, {capture: true, passive: false});
this.closeBtn.addEventListener('click', (e) => { this.closeBtn.addEventListener('click', (e) => {
cancelEvent(e); cancelEvent(e);
@ -831,7 +843,7 @@ export class AppImManager {
} }
} }
public onScroll(e: Event) { public onScroll() {
if(this.onScrollRAF) window.cancelAnimationFrame(this.onScrollRAF); if(this.onScrollRAF) window.cancelAnimationFrame(this.onScrollRAF);
// * В таком случае, кнопка не будет моргать если чат в самом низу, и правильно отработает случай написания нового сообщения и проскролла вниз // * В таком случае, кнопка не будет моргать если чат в самом низу, и правильно отработает случай написания нового сообщения и проскролла вниз
@ -854,10 +866,10 @@ export class AppImManager {
} }
if(this.scrollable.isScrolledDown) { if(this.scrollable.isScrolledDown) {
this.scroll.parentElement.classList.add('scrolled-down'); this.bubblesContainer.classList.add('scrolled-down');
this.scrolledDown = true; this.scrolledDown = true;
} else if(this.scroll.parentElement.classList.contains('scrolled-down')) { } else if(this.bubblesContainer.classList.contains('scrolled-down')) {
this.scroll.parentElement.classList.remove('scrolled-down'); this.bubblesContainer.classList.remove('scrolled-down');
this.scrolledDown = false; this.scrolledDown = false;
} }
@ -866,7 +878,7 @@ export class AppImManager {
} }
public setScroll() { public setScroll() {
this.scrollable = new Scrollable(this.bubblesContainer, 'IM', this.chatInner, 300); this.scrollable = new Scrollable(this.bubblesContainer/* .firstElementChild */ as HTMLElement, 'IM', this.chatInner, 300);
/* const getScrollOffset = () => { /* const getScrollOffset = () => {
//return Math.round(Math.max(300, appPhotosManager.windowH / 1.5)); //return Math.round(Math.max(300, appPhotosManager.windowH / 1.5));
@ -880,14 +892,14 @@ export class AppImManager {
this.scrollable = new Scrollable(this.bubblesContainer, 'y', 'IM', this.chatInner, getScrollOffset()); */ this.scrollable = new Scrollable(this.bubblesContainer, 'y', 'IM', this.chatInner, getScrollOffset()); */
this.scroll = this.scrollable.container; this.scroll = this.scrollable.container;
this.bubblesContainer.append(this.goDownBtn); this.bubblesContainer/* .firstElementChild */.append(this.goDownBtn);
this.scrollable.onScrolledTop = () => this.loadMoreHistory(true); this.scrollable.onScrolledTop = () => this.loadMoreHistory(true);
this.scrollable.onScrolledBottom = () => this.loadMoreHistory(false); this.scrollable.onScrolledBottom = () => this.loadMoreHistory(false);
//this.scrollable.attachSentinels(undefined, 300); //this.scrollable.attachSentinels(undefined, 300);
this.scroll.addEventListener('scroll', this.onScroll.bind(this)); this.scroll.addEventListener('scroll', this.onScroll.bind(this));
this.scroll.parentElement.classList.add('scrolled-down'); this.bubblesContainer.classList.add('scrolled-down');
if(isTouchSupported) { if(isTouchSupported) {
this.scroll.addEventListener('touchmove', () => { this.scroll.addEventListener('touchmove', () => {
@ -1289,9 +1301,18 @@ export class AppImManager {
this.chatInner.classList.toggle('has-rights', hasRights); this.chatInner.classList.toggle('has-rights', hasRights);
const canWrite = (!isChannel || hasRights) && (peerID < 0 || appUsersManager.canSendToUser(peerID)); const canWrite = (!isChannel || hasRights) && (peerID < 0 || appUsersManager.canSendToUser(peerID));
this.chatInput.style.display = canWrite ? '' : 'none'; //const needToChangeInputDisplay = !(!this.chatInput.classList.contains('is-hidden') && canWrite);
//this.chatInput.style.display = needToChangeInputDisplay ? 'none' : '';
this.chatInner.classList.toggle('is-chat-input-hidden', !canWrite); this.chatInput.style.display = '';
this.chatInput.classList.toggle('is-hidden', !canWrite);
this.bubblesContainer.classList.toggle('is-chat-input-hidden', !canWrite);
// const noTransition = [this.columnEl/* appSidebarRight.sidebarEl, this.backgroundEl,
// this.bubblesContainer, this.chatInput, this.chatInner,
// this.chatInputC.replyElements.container */];
// noTransition.forEach(el => {
// el.classList.add('no-transition-all');
// });
this.topbar.classList.remove('is-pinned-shown'); this.topbar.classList.remove('is-pinned-shown');
this.topbar.style.display = ''; this.topbar.style.display = '';
@ -1308,6 +1329,13 @@ export class AppImManager {
this.setPinnedMessage(); this.setPinnedMessage();
window.requestAnimationFrame(() => { window.requestAnimationFrame(() => {
/* noTransition.forEach(el => {
el.classList.remove('no-transition-all');
}); */
/* if(needToChangeInputDisplay) {
this.chatInput.style.display = '';
} */
let title = ''; let title = '';
if(this.peerID == this.myID) title = 'Saved Messages'; if(this.peerID == this.myID) title = 'Saved Messages';
else title = appPeersManager.getPeerTitle(this.peerID); else title = appPeersManager.getPeerTitle(this.peerID);

15
src/lib/appManagers/appMessagesManager.ts

@ -2018,7 +2018,7 @@ export class AppMessagesManager {
withMyScore: true withMyScore: true
}> = {}) { }> = {}) {
peerID = appPeersManager.getPeerMigratedTo(peerID) || peerID; peerID = appPeersManager.getPeerMigratedTo(peerID) || peerID;
mids = mids.sort((a, b) => a - b); mids = mids.slice().sort((a, b) => a - b);
const splitted = appMessagesIDsManager.splitMessageIDsByChannels(mids); const splitted = appMessagesIDsManager.splitMessageIDsByChannels(mids);
const promises: Promise<void>[] = []; const promises: Promise<void>[] = [];
@ -2442,6 +2442,9 @@ export class AppMessagesManager {
case 'messageMediaPhoto': case 'messageMediaPhoto':
messageText += '<i>Photo' + (message.message ? ', ' : '') + '</i>'; messageText += '<i>Photo' + (message.message ? ', ' : '') + '</i>';
break; break;
case 'messageMediaDice':
messageText += RichTextProcessor.wrapEmojiText(message.media.emoticon);
break;
case 'messageMediaGeo': case 'messageMediaGeo':
messageText += '<i>Geolocation</i>'; messageText += '<i>Geolocation</i>';
break; break;
@ -2471,6 +2474,7 @@ export class AppMessagesManager {
break; break;
default: default:
//messageText += message.media._;
///////this.log.warn('Got unknown message.media type!', message); ///////this.log.warn('Got unknown message.media type!', message);
break; break;
} }
@ -2660,6 +2664,15 @@ export class AppMessagesManager {
return true; return true;
} }
public canDeleteMessage(messageID: number) {
const message = this.messagesStorage[messageID];
if(message) {
return message.peerID > 0 || message.fromID == $rootScope.myID || appChatsManager.hasRights(message.peerID, 'deleteRevoke');
} else {
return false;
}
}
public applyConversations(dialogsResult: MessagesPeerDialogs.messagesPeerDialogs) { public applyConversations(dialogsResult: MessagesPeerDialogs.messagesPeerDialogs) {
// * В эту функцию попадут только те диалоги, в которых есть read_inbox_max_id и read_outbox_max_id, в отличие от тех, что будут в getTopMessages // * В эту функцию попадут только те диалоги, в которых есть read_inbox_max_id и read_outbox_max_id, в отличие от тех, что будут в getTopMessages

10
src/lib/richtextprocessor.ts

@ -349,12 +349,12 @@ namespace RichTextProcessor {
entities: MessageEntity[], entities: MessageEntity[],
contextSite: string, contextSite: string,
highlightUsername: string, highlightUsername: string,
noLinks: boolean, noLinks: true,
noLinebreaks: boolean, noLinebreaks: true,
noCommands: boolean, noCommands: true,
fromBot: boolean, fromBot: boolean,
noTextFormat: boolean, noTextFormat: true,
nested?: boolean, nested?: true,
contextHashtag?: string contextHashtag?: string
}> = {}) { }> = {}) {
if(!text || !text.length) { if(!text || !text.length) {

11
src/lib/utils.ts

@ -607,4 +607,15 @@ export function cancelSelection() {
} }
} }
export function getSelectedText() {
if(window.getSelection) {
return window.getSelection().toString();
// @ts-ignore
} else if(document.selection) {
// @ts-ignore
return document.selection.createRange().text;
}
return '';
}
//(window as any).splitStringByLength = splitStringByLength; //(window as any).splitStringByLength = splitStringByLength;

8
src/scss/components/_global.scss

@ -88,6 +88,14 @@ Utility Classes
user-select: none; user-select: none;
} }
/* .no-transition {
transition: none !important;
&-all, &-all * {
transition: none !important;
}
} */
.center-align, .text-center { .center-align, .text-center {
text-align: center; text-align: center;
} }

192
src/scss/partials/_chat.scss

@ -214,6 +214,7 @@ $chat-helper-size: 39px;
} }
#chat-input { #chat-input {
--translateY: 0;
display: flex; display: flex;
width: 100%; width: 100%;
max-width: 100%; max-width: 100%;
@ -222,6 +223,8 @@ $chat-helper-size: 39px;
flex: 0 0 auto; /* Forces side columns to stay same width */ flex: 0 0 auto; /* Forces side columns to stay same width */
position: relative; position: relative;
//overflow: hidden; //overflow: hidden;
transition: transform var(--layer-transition);
transform: translateY(var(--translateY));
/* // * for no ESG top /* // * for no ESG top
flex: 1 1 auto; flex: 1 1 auto;
@ -235,10 +238,10 @@ $chat-helper-size: 39px;
@include respond-to(medium-screens) { @include respond-to(medium-screens) {
width: calc(100% - var(--right-column-width)); width: calc(100% - var(--right-column-width));
transition: transform var(--layer-transition); //transition: transform var(--layer-transition);
body.is-right-column-shown & { body.is-right-column-shown & {
transform: translate3d(calc(var(--right-column-width) / -2), 0, 0); transform: translate3d(calc(var(--right-column-width) / -2), var(--translateY), 0);
} }
body.animation-level-0 & { body.animation-level-0 & {
@ -246,20 +249,31 @@ $chat-helper-size: 39px;
} }
} }
&.is-hidden {
--translateY: 100%;
transform: translate3d(0, var(--translateY), 0);
position: absolute;
bottom: 0;
#bubbles.is-selecting:not(.backwards) ~ & {
--translateY: 0;
}
}
.chat-input-container { .chat-input-container {
--padding-horizontal: #{$chat-padding-handhelds};
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
max-width: var(--messages-container-width); max-width: var(--messages-container-width);
margin: 0 auto; margin: 0 auto;
width: 100%; width: 100%;
padding: 0 $chat-padding-handhelds; padding: 0 var(--padding-horizontal);
flex: 0 0 auto; flex: 0 0 auto;
position: relative; position: relative;
@include respond-to(not-handhelds) { @include respond-to(not-handhelds) {
padding-left: $chat-padding; --padding-horizontal: #{$chat-padding};
padding-right: $chat-padding;
padding-bottom: 21px; padding-bottom: 21px;
} }
@ -327,17 +341,16 @@ $chat-helper-size: 39px;
top: 0; top: 0;
// here percents can be used since there are no other transforms // here percents can be used since there are no other transforms
transform: translateX(calc(-100% + #{-1rem + -$btn-send-margin})); transform: translateX(calc(-100% + var(--padding-horizontal) * -1 + #{-$btn-send-margin}));
@include respond-to(handhelds) { /* @include respond-to(handhelds) {
transform: translateX(calc(-100% + #{-.5rem + -$btn-send-margin})); transform: translateX(calc(-100% + #{-.5rem + -$btn-send-margin}));
} } */
} }
.btn-send-container { .btn-send-container {
flex: 0 0 auto; position: absolute;
position: relative; right: var(--padding-horizontal);
align-self: flex-end;
z-index: 2; z-index: 2;
} }
@ -625,6 +638,10 @@ $chat-helper-size: 39px;
.chat-background { .chat-background {
overflow: hidden; overflow: hidden;
&.no-transition:before {
transition: none !important;
}
&, &:before { &, &:before {
position: absolute !important; position: absolute !important;
top: 0; top: 0;
@ -884,7 +901,7 @@ $chat-helper-size: 39px;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
width: calc(100% - #{$chat-input-size + $btn-send-margin}); width: calc(100% - #{$chat-input-size + $btn-send-margin});
max-width: 100%; max-width: calc(100% - #{$chat-input-size + $btn-send-margin});
justify-content: center; justify-content: center;
background-color: #fff; background-color: #fff;
border-radius: 12px; border-radius: 12px;
@ -920,8 +937,13 @@ $chat-helper-size: 39px;
@include respond-to(handhelds) { @include respond-to(handhelds) {
--padding: .5px .5rem; --padding: .5px .5rem;
width: calc(100% - #{$chat-input-handhelds-size + $btn-send-margin}); width: calc(100% - #{$chat-input-handhelds-size + $btn-send-margin});
max-width: calc(100% - #{$chat-input-handhelds-size + $btn-send-margin});
min-height: $chat-input-handhelds-size; min-height: $chat-input-handhelds-size;
} }
@media only screen and (max-width: 420px) {
max-width: 100%;
}
@include respond-to(esg-bottom) { @include respond-to(esg-bottom) {
--padding: .5px .5rem; --padding: .5px .5rem;
@ -1031,21 +1053,54 @@ $chat-helper-size: 39px;
border-radius: inherit; border-radius: inherit;
padding: inherit; padding: inherit;
user-select: none; user-select: none;
font-size: 15px;
&-count { &-count {
color: #000; color: #000;
font-weight: 500; font-weight: 500;
flex-grow: 1; flex-grow: 1;
padding-left: .5rem; white-space: nowrap;
//padding-left: .5rem;
}
.btn-icon {
margin-left: 6px;
maRgin-top: 6px;
color: #3f454a;
height: 42px;
width: 42px;
} }
.btn-primary { .btn-primary {
height: 2.5rem; height: 2.5rem;
width: 7.5rem; width: auto;
@include respond-to(handhelds) {
padding: 0 .5rem;
}
@media only screen and (max-width: 380px) {
font-size: 0;
&:before {
margin: 0;
}
}
}
&-forward {
&:before {
margin-right: 14px;
}
} }
.btn-primary + .btn-primary { &-delete {
margin-left: .75rem; margin-right: .625rem;
margin-left: .375rem;
&:before {
margin-right: 10px;
}
} }
} }
@ -1086,6 +1141,7 @@ $chat-helper-size: 39px;
flex: 1 1 auto; /* Lets middle column shrink/grow to available width */ flex: 1 1 auto; /* Lets middle column shrink/grow to available width */
//overflow: hidden; //overflow: hidden;
position: relative; position: relative;
transform: translateY(var(--translateY)); transform: translateY(var(--translateY));
transition: transform var(--layer-transition); transition: transform var(--layer-transition);
@ -1096,9 +1152,54 @@ $chat-helper-size: 39px;
.chat-container.is-helper-active & { .chat-container.is-helper-active & {
&:not(.is-selecting), &.is-selecting.backwards { &:not(.is-selecting), &.is-selecting.backwards {
--translateY: -#{$chat-helper-size}; --translateY: -#{$chat-helper-size};
#bubbles-inner {
transform: translateY(calc(var(--translateY) * -1));
//margin-top: $chat-helper-size;
//transition: none;
}
} }
} }
&.is-chat-input-hidden.is-selecting:not(.backwards) {
--translateY: -79px;
#bubbles-inner {
transform: translateY(calc(var(--translateY) * -1));
//margin-top: $chat-helper-size;
//transition: none;
}
}
/* #bubbles-transform-helper {
width: 100%;
height: 100%;
max-height: 100%;
position: relative;
transform: translateY(var(--translateY));
transition: transform var(--layer-transition); */
> .scrollable {
height: auto;
/* position: absolute;
bottom: 0;
left: 0; */
//position: relative; // неизвестно зачем это было
//display: flex; // for end
//flex-direction: unset;
display: block;
/* 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;
}
}
//}
// ! WARNING, НЕЛЬЗЯ СТАВИТЬ ТРАНСФОРМ КРОМЕ TRANSLATEZ(0) НА БЛОК С OVERFLOW, ОН БУДЕТ ПРЫГАТЬ ВВЕРХ ПРИ ВКЛЮЧЕННОМ ПРАВИЛЕ И ЭТО НЕ ИСПРАВИТЬ JS'ОМ! // ! WARNING, НЕЛЬЗЯ СТАВИТЬ ТРАНСФОРМ КРОМЕ TRANSLATEZ(0) НА БЛОК С OVERFLOW, ОН БУДЕТ ПРЫГАТЬ ВВЕРХ ПРИ ВКЛЮЧЕННОМ ПРАВИЛЕ И ЭТО НЕ ИСПРАВИТЬ JS'ОМ!
@include respond-to(medium-screens) { @include respond-to(medium-screens) {
//transition: transform var(--layer-transition); //transition: transform var(--layer-transition);
@ -1113,9 +1214,6 @@ $chat-helper-size: 39px;
} }
&.is-selecting { &.is-selecting {
cursor: default !important;
user-select: none;
&:not(.backwards) .is-in .bubble__container { &:not(.backwards) .is-in .bubble__container {
transform: translateX(2.5rem); transform: translateX(2.5rem);
} }
@ -1132,42 +1230,25 @@ $chat-helper-size: 39px;
/* &.is-selecting > .scrollable::-webkit-scrollbar { /* &.is-selecting > .scrollable::-webkit-scrollbar {
display: none; display: none;
} */ } */
> .scrollable {
height: auto;
/* position: absolute;
bottom: 0;
left: 0; */
//position: relative; // неизвестно зачем это было
//display: flex; // for end
//flex-direction: unset;
display: block;
/* 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;
}
}
&:not(.scrolled-down):not(.search-results-active) { &:not(.scrolled-down):not(.search-results-active) {
> .scrollable { //> #bubbles-transform-helper {
-webkit-mask-image: -webkit-linear-gradient(bottom, transparent, #000 20px); // ! these lines will blur messages if chat input helper is active
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; cursor: pointer;
--translateY: 0; --translateY: 0;
opacity: 1; opacity: 1;
/* &.is-broadcast { /* &.is-broadcast {
--translateY: 79px !important; --translateY: 79px !important;
} */ } */
} }
//}
} }
.preloader { .preloader {
@ -1201,6 +1282,11 @@ $chat-helper-size: 39px;
padding: 0 1rem; padding: 0 1rem;
max-width: var(--messages-container-width); max-width: var(--messages-container-width);
transition: transform var(--layer-transition);
transform: translateY(0);
/* transition: margin-top var(--layer-transition);
transition-delay: .2s; */
@include respond-to(medium-screens) { @include respond-to(medium-screens) {
width: calc(100% - var(--right-column-width)); width: calc(100% - var(--right-column-width));
} }
@ -1237,9 +1323,9 @@ $chat-helper-size: 39px;
} }
} }
&.is-chat-input-hidden { /* #bubbles.is-chat-input-hidden & {
padding-bottom: 55px; padding-bottom: 55px;
} } */
&:not(.is-channel), &.is-chat { &:not(.is-channel), &.is-chat {
.message { .message {

34
src/scss/partials/_leftSidebar.scss

@ -466,7 +466,6 @@
} }
.tgico-add:before { .tgico-add:before {
content: "\e903";
font-size: 24px; font-size: 24px;
margin-right: 6px; margin-right: 6px;
} }
@ -619,17 +618,7 @@
color: #50a2e9; color: #50a2e9;
} }
.included-chats-container { .popup-forward, .included-chats-container {
.sidebar-left-h2 {
color: #707579;
font-size: 15px;
font-weight: 500;
padding: 6px 24px 8px 24px;
@include respond-to(handhelds) {
padding: 6px 16px 8px 16px;
}
}
.selector { .selector {
ul { ul {
li > .rp { li > .rp {
@ -647,10 +636,6 @@
height: 46px; height: 46px;
} }
span.user-title {
font-weight: 500;
}
.user-caption { .user-caption {
padding: 0px 0px 0 14px; padding: 0px 0px 0 14px;
margin-top: -2px; margin-top: -2px;
@ -660,7 +645,24 @@
font-size: 15px; font-size: 15px;
margin-top: 2px; margin-top: 2px;
} }
}
}
}
.included-chats-container {
.sidebar-left-h2 {
color: #707579;
font-size: 15px;
font-weight: 500;
padding: 6px 24px 8px 24px;
@include respond-to(handhelds) {
padding: 6px 16px 8px 16px;
}
}
.selector {
ul {
.checkbox { .checkbox {
margin-top: 10px; margin-top: 10px;
} }

4
src/scss/partials/_selector.scss

@ -129,10 +129,6 @@
p { p {
height: 24px; height: 24px;
} }
span.user-title {
font-weight: normal;
}
span.user-last-message { span.user-last-message {
font-size: 14px; font-size: 14px;

6
src/scss/partials/popups/_forward.scss

@ -6,15 +6,15 @@
width: 420px; width: 420px;
max-width: 420px; max-width: 420px;
//padding: 12px 20px 32.5px; //padding: 12px 20px 32.5px;
padding: .75rem 0 0 0; padding: 9px 0 0 0;
max-height: unquote('min(40.625rem, 100%)'); max-height: unquote('min(40.625rem, 100%)');
height: 40.625rem; height: 40.625rem;
} }
&-header { &-header {
flex: 0 0 auto; flex: 0 0 auto;
margin-bottom: 9px; margin-bottom: 4px;
padding: 0 .75rem; padding: 0 1rem;
} }
&-title { &-title {

8
src/scss/partials/popups/_stickers.scss

@ -60,12 +60,16 @@
} }
&-sticker { &-sticker {
width: 80px; width: var(--esg-sticker-size);
height: 80px; height: var(--esg-sticker-size);
margin-bottom: 2px; margin-bottom: 2px;
justify-self: center; justify-self: center;
cursor: pointer; cursor: pointer;
@include respond-to(handhelds) {
margin-bottom: 8px;
}
&:hover { &:hover {
border-radius: 12px; border-radius: 12px;
background-color: var(--color-gray-hover); background-color: var(--color-gray-hover);

22
src/scss/style.scss

@ -840,9 +840,10 @@ input:focus, button:focus {
overflow: hidden; overflow: hidden;
position: relative; position: relative;
padding: 0; // new padding: 0; // new
transition: .2s opacity;
html.no-touch &:hover { html.no-touch &:hover {
transition: .2s background-color; transition: .2s background-color, .2s opacity;
background: darken($color-blue, 8%); background: darken($color-blue, 8%);
} }
@ -852,24 +853,31 @@ input:focus, button:focus {
left: auto; left: auto;
} }
// * tgico &:disabled {
&:before { pointer-events: none !important;
color: #707579; opacity: .25;
font-size: 1.5rem;
margin-right: 1rem;
} }
} }
// ! example: multiselect input
.btn-transparent { .btn-transparent {
color: #000; color: #000;
background-color: transparent; background-color: transparent;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; padding: 0 .875rem;
//width: auto;
html.no-touch &:hover { html.no-touch &:hover {
background-color: var(--color-gray-hover); background-color: var(--color-gray-hover);
} }
// * tgico
&:before {
color: #707579;
font-size: 1.5rem;
margin-right: 1rem;
}
} }
.btn-primary.btn-circle { .btn-primary.btn-circle {

Loading…
Cancel
Save