diff --git a/src/components/appMediaViewer.ts b/src/components/appMediaViewer.ts index e47a53d4..3f65bbe2 100644 --- a/src/components/appMediaViewer.ts +++ b/src/components/appMediaViewer.ts @@ -19,6 +19,7 @@ import { ButtonMenuItemOptions } from "./buttonMenu"; import ButtonMenuToggle from "./buttonMenuToggle"; import { LazyLoadQueueBase } from "./lazyLoadQueue"; import { renderImageFromUrl } from "./misc"; +import PopupForward from "./popupForward"; import ProgressivePreloader from "./preloader"; import appSidebarRight, { AppSidebarRight } from "./sidebarRight"; import SwipeHandler from "./swipeHandler"; @@ -231,11 +232,11 @@ class AppMediaViewerBase { appSidebarRight.forwardTab.closeBtn.click(); }); - } + } */ window.removeEventListener('keydown', this.onKeyDown); @@ -794,10 +795,10 @@ class AppMediaViewerBase setTimeout(resolve, 200)); - } + } */ } /* if(this.nextTargets.length < 10 && this.loadMore) { @@ -1132,7 +1133,10 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet onForwardClick = (e: MouseEvent) => { if(this.currentMessageID) { - appSidebarRight.forwardTab.open([this.currentMessageID]); + //appSidebarRight.forwardTab.open([this.currentMessageID]); + new PopupForward([this.currentMessageID], () => { + return this.close(); + }); } }; diff --git a/src/components/appSelectPeers.ts b/src/components/appSelectPeers.ts index 40916c73..83fcf834 100644 --- a/src/components/appSelectPeers.ts +++ b/src/components/appSelectPeers.ts @@ -13,15 +13,15 @@ type PeerType = 'contacts' | 'dialogs'; // TODO: правильная сортировка для addMembers, т.е. для peerType: 'contacts', потому что там идут сначала контакты - потом неконтакты, а должно всё сортироваться по имени let loadedAllDialogs = false, loadAllDialogsPromise: Promise; -export class AppSelectPeers { +export default class AppSelectPeers { public container = document.createElement('div'); public list = document.createElement('ul'); public chatsContainer = document.createElement('div'); public scrollable: Scrollable; public selectedScrollable: Scrollable; - public selectedContainer = document.createElement('div'); - public input = document.createElement('input'); + public selectedContainer: HTMLElement; + public input: HTMLInputElement; //public selected: {[peerID: number]: HTMLElement} = {}; public selected = new Set(); @@ -37,24 +37,49 @@ export class AppSelectPeers { private loadedWhat: Partial<{[k in 'dialogs' | 'archived' | 'contacts']: true}> = {}; - constructor(private appendTo: HTMLElement, private onChange?: (length: number) => void, private peerType: PeerType[] = ['dialogs'], onFirstRender?: () => void, private renderResultsFunc?: (peerIDs: number[]) => void, private chatRightsAction?: ChatRights) { + 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'); if(!this.renderResultsFunc) { this.renderResultsFunc = this.renderResults; } - let topContainer = document.createElement('div'); - topContainer.classList.add('selector-search-container'); - - this.selectedContainer.classList.add('selector-search'); + this.input = document.createElement('input'); + this.input.classList.add('selector-search-input'); this.input.placeholder = !peerType.includes('dialogs') ? 'Add People...' : 'Select chat'; this.input.type = 'text'; - this.selectedContainer.append(this.input); - topContainer.append(this.selectedContainer); - this.selectedScrollable = new Scrollable(topContainer); - let delimiter = document.createElement('hr'); + if(multiSelect) { + let topContainer = document.createElement('div'); + topContainer.classList.add('selector-search-container'); + + this.selectedContainer = document.createElement('div'); + this.selectedContainer.classList.add('selector-search'); + + this.selectedContainer.append(this.input); + topContainer.append(this.selectedContainer); + this.selectedScrollable = new Scrollable(topContainer); + + let delimiter = document.createElement('hr'); + + this.selectedContainer.addEventListener('click', (e) => { + if(this.freezed) return; + let target = e.target as HTMLElement; + target = findUpClassName(target, 'selector-user'); + + if(!target) return; + + const peerID = target.dataset.key; + const li = this.chatsContainer.querySelector('[data-peerid="' + peerID + '"]') as HTMLElement; + if(!li) { + this.remove(+peerID || peerID); + } else { + li.click(); + } + }); + + this.container.append(topContainer, delimiter); + } this.chatsContainer.classList.add('chats-container'); this.chatsContainer.append(this.list); @@ -70,6 +95,12 @@ export class AppSelectPeers { let key: any = target.getAttribute('data-peerID'); key = +key || key; + + if(!this.multiSelect) { + this.add(key); + return; + } + target.classList.toggle('active'); if(this.selected.has(key)) { this.remove(key); @@ -81,22 +112,6 @@ export class AppSelectPeers { checkbox.checked = !checkbox.checked; }); - this.selectedContainer.addEventListener('click', (e) => { - if(this.freezed) return; - let target = e.target as HTMLElement; - target = findUpClassName(target, 'selector-user'); - - if(!target) return; - - const peerID = target.dataset.key; - const li = this.chatsContainer.querySelector('[data-peerid="' + peerID + '"]') as HTMLElement; - if(!li) { - this.remove(+peerID || peerID); - } else { - li.click(); - } - }); - this.input.addEventListener('input', () => { const value = this.input.value; if(this.query != value) { @@ -125,7 +140,7 @@ export class AppSelectPeers { this.getMoreResults(); }; - this.container.append(topContainer, delimiter, this.chatsContainer); + this.container.append(this.chatsContainer); appendTo.append(this.container); // WARNING TIMEOUT @@ -292,9 +307,11 @@ export class AppSelectPeers { peerIDs.forEach(peerID => { const {dom} = appDialogsManager.addDialog(peerID, this.scrollable, false, false); - const selected = this.selected.has(peerID); - dom.containerEl.insertAdjacentHTML('afterbegin', `
`); - if(selected) dom.listEl.classList.add('active'); + if(this.multiSelect) { + const selected = this.selected.has(peerID); + dom.containerEl.insertAdjacentHTML('afterbegin', `
`); + if(selected) dom.listEl.classList.add('active'); + } let subtitle = ''; if(peerID < 0) { @@ -314,6 +331,13 @@ export class AppSelectPeers { public add(peerID: any, title?: string) { //console.trace('add'); + this.selected.add(peerID); + + if(!this.multiSelect) { + this.onChange(this.selected.size); + return; + } + const div = document.createElement('div'); div.classList.add('selector-user', 'scale-in'); @@ -322,7 +346,6 @@ export class AppSelectPeers { avatarEl.setAttribute('dialog', '1'); div.dataset.key = '' + peerID; - this.selected.add(peerID); if(typeof(peerID) === 'number') { if(title === undefined) { title = peerID == $rootScope.myID ? 'Saved' : appPeersManager.getPeerTitle(peerID, false, true); @@ -346,6 +369,7 @@ export class AppSelectPeers { } public remove(key: any) { + if(!this.multiSelect) return; //const div = this.selected[peerID]; const div = this.selectedContainer.querySelector(`[data-key="${key}"]`) as HTMLElement; div.classList.remove('scale-in'); diff --git a/src/components/button.ts b/src/components/button.ts new file mode 100644 index 00000000..3ac1141d --- /dev/null +++ b/src/components/button.ts @@ -0,0 +1,11 @@ +import { ripple } from "./ripple"; + +const Button = (className: string, options: Partial<{noRipple: true, onlyMobile: true, icon: string}> = {}) => { + const button = document.createElement('button'); + button.className = className + (options.icon ? ' tgico-' + options.icon : ''); + if(!options.noRipple) ripple(button); + if(options.onlyMobile) button.classList.add('only-handhelds'); + return button; +}; + +export default Button; \ No newline at end of file diff --git a/src/components/buttonIcon.ts b/src/components/buttonIcon.ts index 2cfbe30c..64c2ea47 100644 --- a/src/components/buttonIcon.ts +++ b/src/components/buttonIcon.ts @@ -1,10 +1,7 @@ -import { ripple } from "./ripple"; +import Button from "./button"; const ButtonIcon = (className: string, options: Partial<{noRipple: true, onlyMobile: true}> = {}) => { - const button = document.createElement('button'); - button.className = `btn-icon tgico-${className}`; - if(!options.noRipple) ripple(button); - if(options.onlyMobile) button.classList.add('only-handhelds'); + const button = Button('btn-icon', {icon: className, ...options}); return button; }; diff --git a/src/components/chat/contextMenu.ts b/src/components/chat/contextMenu.ts index db2a5d53..3d21ac65 100644 --- a/src/components/chat/contextMenu.ts +++ b/src/components/chat/contextMenu.ts @@ -8,6 +8,7 @@ import { findUpClassName } from "../../lib/utils"; import ButtonMenu, { ButtonMenuItemOptions } from "../buttonMenu"; import { attachContextMenuListener, openBtnMenu, positionMenu } from "../misc"; import { PopupButton } from "../popup"; +import PopupForward from "../popupForward"; import PopupPeer from "../popupPeer"; import appSidebarRight from "../sidebarRight"; @@ -124,7 +125,7 @@ export default class ChatContextMenu { onClick: this.onForwardClick, verify: () => this.msgID > 0 }, { - icon: 'revote', + icon: 'select', text: 'Select', onClick: this.onSelectClick, verify: () => { @@ -133,7 +134,7 @@ export default class ChatContextMenu { }, notDirect: () => true }, { - icon: 'revote', + icon: 'select', text: 'Clear selection', onClick: this.onClearSelectionClick, verify: () => { @@ -155,17 +156,15 @@ export default class ChatContextMenu { private onReplyClick = () => { const message = appMessagesManager.getMessage(this.msgID); const chatInputC = appImManager.chatInputC; - chatInputC.setTopInfo(appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message); - chatInputC.replyToMsgID = this.msgID; - chatInputC.editMsgID = 0; + const f = () => { + chatInputC.setTopInfo('reply', f, appPeersManager.getPeerTitle(message.fromID, true), message.message, undefined, message); + chatInputC.replyToMsgID = this.msgID; + }; + f(); }; private onEditClick = () => { - const message = appMessagesManager.getMessage(this.msgID); - const chatInputC = appImManager.chatInputC; - chatInputC.setTopInfo('Editing', message.message, message.message, message); - chatInputC.replyToMsgID = 0; - chatInputC.editMsgID = this.msgID; + appImManager.chatInputC.initMessageEditing(this.msgID); }; private onCopyClick = () => { @@ -206,7 +205,7 @@ export default class ChatContextMenu { }; private onForwardClick = () => { - appSidebarRight.forwardTab.open([this.msgID]); + new PopupForward([this.msgID]); }; private onSelectClick = () => { diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index ba0b5c02..3b763620 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -4,6 +4,7 @@ import appChatsManager from '../../lib/appManagers/appChatsManager'; import appDocsManager from "../../lib/appManagers/appDocsManager"; import appImManager from "../../lib/appManagers/appImManager"; import appMessagesManager from "../../lib/appManagers/appMessagesManager"; +import appPeersManager from '../../lib/appManagers/appPeersManager'; import appWebPagesManager from "../../lib/appManagers/appWebPagesManager"; import apiManager from "../../lib/mtproto/mtprotoworker"; //import Recorder from '../opus-recorder/dist/recorder.min'; @@ -23,6 +24,8 @@ import { wrapReply } from "../wrappers"; const RECORD_MIN_TIME = 500; const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.'; +type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply'; + export class ChatInput { public pageEl = document.getElementById('page-chats') as HTMLDivElement; public messageInput = document.getElementById('input-message') as HTMLDivElement/* HTMLInputElement */; @@ -48,6 +51,7 @@ export class ChatInput { } = {}; public willSendWebPage: any = null; + public forwardingMids: number[] = []; public replyToMsgID = 0; public editMsgID = 0; public noWebPage: true; @@ -63,6 +67,9 @@ export class ChatInput { private scrollOffsetTop = 0; private scrollDiff = 0; + private helperType: Exclude; + private helperFunc: () => void; + constructor() { this.attachMenu = document.getElementById('attach-file') as HTMLButtonElement; @@ -198,9 +205,8 @@ export class ChatInput { if(this.lastUrl != url) return; //console.log('got webpage: ', webpage); - this.setTopInfo(webpage.site_name || webpage.title, webpage.description || webpage.url); + this.setTopInfo('webpage', () => {}, webpage.site_name || webpage.title, webpage.description || webpage.url); - this.replyToMsgID = 0; delete this.noWebPage; this.willSendWebPage = webpage; } @@ -313,7 +319,7 @@ export class ChatInput { const onBtnSendClick = (e: Event) => { cancelEvent(e); - if(!this.recorder || this.recording || !this.isInputEmpty()) { + if(!this.recorder || this.recording || !this.isInputEmpty() || this.forwardingMids.length) { if(this.recording) { if((Date.now() - this.recordStartTime) < RECORD_MIN_TIME) { this.btnCancelRecord.click(); @@ -487,23 +493,21 @@ export class ChatInput { } this.replyElements.cancelBtn.addEventListener('click', () => { - this.replyElements.container.classList.remove('active'); - this.replyToMsgID = 0; + if(this.willSendWebPage) { + this.noWebPage = true; + this.willSendWebPage = null; - if(this.editMsgID) { - if(this.willSendWebPage) { - let message = appMessagesManager.getMessage(this.editMsgID); - this.setTopInfo('Editing', message.message); - } else { - this.editMsgID = 0; - this.messageInput.innerHTML = ''; + if(this.helperType) { + //if(this.helperFunc) { + this.helperFunc(); + //} - this.updateSendBtn(); + return; } } - this.noWebPage = true; - this.willSendWebPage = null; + this.clearHelper(); + this.updateSendBtn(); }); } @@ -516,7 +520,7 @@ export class ChatInput { public updateSendBtn() { let icon: 'send' | 'record'; - if(!this.recorder || this.recording || !this.isInputEmpty()) icon = 'send'; + if(!this.recorder || this.recording || !this.isInputEmpty() || this.forwardingMids.length) icon = 'send'; else icon = 'record'; this.btnSend.classList.toggle('send', icon == 'send'); @@ -543,18 +547,16 @@ export class ChatInput { if(clearInput) { this.lastUrl = ''; - this.editMsgID = 0; delete this.noWebPage; this.willSendWebPage = null; this.messageInput.innerText = ''; - - this.updateSendBtn(); } if(clearReply || clearInput) { - this.replyToMsgID = 0; - this.replyElements.container.classList.remove('active'); + this.clearHelper(); } + + this.updateSendBtn(); } public sendMessage() { @@ -577,6 +579,10 @@ export class ChatInput { }); } + if(this.forwardingMids.length) { + appMessagesManager.forwardMessages(appImManager.peerID, this.forwardingMids); + } + this.onMessageSent(); } @@ -596,24 +602,72 @@ export class ChatInput { return false; } - public setTopInfo(title: string, subtitle: string, input?: string, message?: any) { - //appImManager.scrollPosition.prepareFor('down'); + public initMessageEditing(mid: number) { + const message = appMessagesManager.getMessage(mid); + + let input = message.message; + const f = () => { + this.setTopInfo('edit', f, 'Editing', message.message, input, message); + this.editMsgID = mid; + input = undefined; + }; + f(); + } + + public initMessagesForward(mids: number[]) { + const f = () => { + //const peerTitles: string[] + const fromIDs = new Set(mids.map(mid => appMessagesManager.getMessage(mid).fromID)); + const onlyFirstName = fromIDs.size > 1; + const peerTitles = [...fromIDs].map(peerID => appPeersManager.getPeerTitle(peerID, true, onlyFirstName)); + + const title = peerTitles.length < 3 ? peerTitles.join(' and ') : peerTitles[0] + ' and ' + (peerTitles.length - 1) + ' others'; + if(mids.length == 1) { + const message = appMessagesManager.getMessage(mids[0]); + this.setTopInfo('forward', f, title, message.message, undefined, message); + } else { + this.setTopInfo('forward', f, title, mids.length + ' forwarded messages'); + } + + this.forwardingMids = mids; + }; + + f(); + } + + public clearHelper(type?: ChatInputHelperType) { + if(this.helperType == 'edit' && type != 'edit') { + this.messageInput.innerText = ''; + } + + this.replyToMsgID = 0; + this.forwardingMids.length = 0; + this.editMsgID = 0; + this.helperType = this.helperFunc = undefined; + this.chatInput.parentElement.classList.remove('is-helper-active'); + } + + public setTopInfo(type: ChatInputHelperType, callerFunc: () => void, title: string, subtitle: string, input?: string, message?: any) { + if(type != 'webpage') { + this.clearHelper(type); + this.helperType = type; + this.helperFunc = callerFunc; + } if(this.replyElements.container.lastElementChild.tagName == 'DIV') { this.replyElements.container.lastElementChild.remove(); this.replyElements.container.append(wrapReply(title, subtitle, message)); } - //this.replyElements.titleEl.innerHTML = title ? RichTextProcessor.wrapEmojiText(title) : ''; - //this.replyElements.subtitleEl.innerHTML = subtitle ? RichTextProcessor.wrapEmojiText(subtitle) : ''; - this.replyElements.container.classList.add('active'); + + this.chatInput.parentElement.classList.add('is-helper-active'); if(input !== undefined) { this.messageInput.innerHTML = input ? RichTextProcessor.wrapRichText(input) : ''; - - this.updateSendBtn(); } - //appImManager.scrollPosition.restore(); + setTimeout(() => { + this.updateSendBtn(); + }, 0); } public saveScroll() { diff --git a/src/components/chat/selection.ts b/src/components/chat/selection.ts index a245da51..f9c06381 100644 --- a/src/components/chat/selection.ts +++ b/src/components/chat/selection.ts @@ -1,13 +1,143 @@ +import { isTouchSupported } from "../../helpers/touchSupport"; import type { AppImManager } from "../../lib/appManagers/appImManager"; import type { AppMessagesManager } from "../../lib/appManagers/appMessagesManager"; +import { cancelEvent, cancelSelection, findUpClassName } from "../../lib/utils"; +import Button from "../button"; +import ButtonIcon from "../buttonIcon"; import CheckboxField from "../checkbox"; +import PopupForward from "../popupForward"; +import { toast } from "../toast"; + +const SetTransition = (element: HTMLElement, className: string, forwards: boolean, duration: number, onTransitionEnd?: () => void) => { + const timeout = element.dataset.timeout; + if(timeout !== undefined) { + clearTimeout(+timeout); + } + + if(forwards) { + element.classList.add(className); + } + + element.classList.add('animating'); + + element.classList.toggle('backwards', !forwards); + element.dataset.timeout = '' + setTimeout(() => { + delete element.dataset.timeout; + if(!forwards) { + element.classList.remove('backwards', className); + } + + element.classList.remove('animating'); + + onTransitionEnd && onTransitionEnd(); + }, duration); +}; + +const MAX_SELECTION_LENGTH = 100; +const MIN_CLICK_MOVE = 32; // minimum bubble height export default class ChatSelection { public selectedMids: Set = new Set(); public isSelecting = false; + private selectionContainer: HTMLElement; + private selectionCountEl: HTMLElement; + constructor(private appImManager: AppImManager, private appMessagesManager: AppMessagesManager) { + if(isTouchSupported) return; + + const bubblesContainer = appImManager.bubblesContainer; + bubblesContainer.addEventListener('mousedown', (e) => { + //console.log('selection mousedown', e); + if(e.button != 0) { // LEFT BUTTON + return; + } + + const seen: Set = new Set(); + let selecting: boolean; + /* let good = false; + const {x, y} = e; */ + + /* const bubbles = appImManager.bubbles; + for(const mid in bubbles) { + const bubble = bubbles[mid]; + bubble.addEventListener('mouseover', () => { + console.log('mouseover'); + }, {once: true}); + } */ + + //const foundTargets: Map = new Map(); + const onMouseMove = (e: MouseEvent) => { + /* if(!good) { + if(Math.abs(e.x - x) > MIN_CLICK_MOVE || Math.abs(e.y - y) > MIN_CLICK_MOVE) { + good = true; + } else { + return; + } + } */ + + /* if(foundTargets.has(e.target as HTMLElement)) return; + foundTargets.set(e.target as HTMLElement, true); */ + const bubble = findUpClassName(e.target, 'bubble'); + if(!bubble) { + console.error('found no bubble', e); + return; + } + + const mid = +bubble.dataset.mid; + if(!mid) return; + + if(e.target != bubble && selecting === undefined) { + bubblesContainer.removeEventListener('mousemove', onMouseMove); + document.removeEventListener('mouseup', onMouseUp); + return; + } + + if(!seen.has(mid)) { + const isBubbleSelected = this.selectedMids.has(mid); + if(selecting === undefined) { + appImManager.scrollable.container.classList.add('no-select'); + selecting = !isBubbleSelected; + } + + seen.add(mid); + + if((selecting && !isBubbleSelected) || (!selecting && isBubbleSelected)) { + if(!this.selectedMids.size) { + if(seen.size == 2) { + [...seen].forEach(mid => { + const mounted = this.appImManager.getMountedBubble(mid); + if(mounted) { + this.toggleByBubble(mounted.bubble); + } + }) + } + } else { + this.toggleByBubble(bubble); + } + } + } + //console.log('onMouseMove', target); + }; + + const onMouseUp = (e: MouseEvent) => { + if(seen.size) { + window.addEventListener('click', (e) => { + cancelEvent(e); + }, {capture: true, once: true, passive: false}); + } + + bubblesContainer.removeEventListener('mousemove', onMouseMove); + appImManager.scrollable.container.classList.remove('no-select'); + + // ! CANCEL USER SELECTION ! + cancelSelection(); + }; + + bubblesContainer.addEventListener('mousemove', onMouseMove); + document.addEventListener('mouseup', onMouseUp, {once: true}); + }); } public toggleBubbleCheckbox(bubble: HTMLElement, show: boolean) { @@ -25,31 +155,79 @@ export default class ChatSelection { bubble.classList.add('is-selected'); } - bubble.append(checkboxField.label); + bubble.prepend(checkboxField.label); } else if(hasCheckbox) { - bubble.lastElementChild.remove(); + bubble.firstElementChild.remove(); } } public getCheckboxInputFromBubble(bubble: HTMLElement) { - return bubble.lastElementChild.tagName == 'LABEL' && bubble.lastElementChild.firstElementChild as HTMLInputElement; + return bubble.firstElementChild.tagName == 'LABEL' && bubble.firstElementChild.firstElementChild as HTMLInputElement; } - public toggleSelection() { + public updateForwardContainer() { + if(!this.selectedMids.size) return; + this.selectionCountEl.innerText = this.selectedMids.size + ' Message' + (this.selectedMids.size == 1 ? '' : 's'); + + } + + public toggleSelection(toggleCheckboxes = true) { const wasSelecting = this.isSelecting; this.isSelecting = this.selectedMids.size > 0; if(wasSelecting == this.isSelecting) return; - this.appImManager.bubblesContainer.classList.toggle('is-selecting', !!this.selectedMids.size); + const bubblesContainer = this.appImManager.bubblesContainer; + //bubblesContainer.classList.toggle('is-selecting', !!this.selectedMids.size); - for(const mid in this.appImManager.bubbles) { - const bubble = this.appImManager.bubbles[mid]; - this.toggleBubbleCheckbox(bubble, this.isSelecting); + SetTransition(bubblesContainer, 'is-selecting', !!this.selectedMids.size, 200, () => { + if(!this.isSelecting) { + this.selectionContainer.remove(); + this.selectionContainer = null; + } + }); + + //const chatInput = this.appImManager.chatInput; + + if(this.isSelecting) { + if(!this.selectionContainer) { + const inputMessageDiv = document.querySelector('.input-message'); + this.selectionContainer = document.createElement('div'); + this.selectionContainer.classList.add('selection-container'); + + const btnCancel = ButtonIcon('close', {noRipple: true}); + btnCancel.addEventListener('click', this.cancelSelection, {once: true}); + + this.selectionCountEl = document.createElement('div'); + this.selectionCountEl.classList.add('selection-container-count'); + + const btnForward = Button('btn-primary btn-transparent', {icon: 'forward'}); + btnForward.append('Forward'); + + btnForward.addEventListener('click', () => { + new PopupForward([...this.selectedMids], () => { + this.cancelSelection(); + }); + }); + + const btnDelete = Button('btn-primary btn-transparent danger', {icon: 'delete'}); + btnDelete.append('Delete'); + + this.selectionContainer.append(btnCancel, this.selectionCountEl, btnForward, btnDelete); + + inputMessageDiv.append(this.selectionContainer); + } + } + + if(toggleCheckboxes) { + for(const mid in this.appImManager.bubbles) { + const bubble = this.appImManager.bubbles[mid]; + this.toggleBubbleCheckbox(bubble, this.isSelecting); + } } } - public cancelSelection() { + public cancelSelection = () => { for(const mid of this.selectedMids) { const bubble = this.appImManager.bubbles[mid]; if(bubble) { @@ -59,12 +237,12 @@ export default class ChatSelection { this.selectedMids.clear(); this.toggleSelection(); - } + cancelSelection(); + }; public cleanup() { - this.isSelecting = false; this.selectedMids.clear(); - this.appImManager.bubblesContainer.classList.remove('is-selecting'); + this.toggleSelection(false); } public toggleByBubble(bubble: HTMLElement) { @@ -75,6 +253,25 @@ export default class ChatSelection { if(found) { mids.forEach(mid => this.selectedMids.delete(mid)); } else { + let diff = MAX_SELECTION_LENGTH - this.selectedMids.size - mids.length; + if(diff < 0) { + toast('Max selection count reached.'); + return; + /* const it = this.selectedMids.values(); + do { + const mid = it.next().value; + const mounted = this.appImManager.getMountedBubble(mid); + if(mounted) { + this.toggleByBubble(mounted.bubble); + } else { + const mids = this.appMessagesManager.getMidsByMid(mid); + for(const mid of mids) { + this.selectedMids.delete(mid); + } + } + } while(this.selectedMids.size > MAX_SELECTION_LENGTH); */ + } + mids.forEach(mid => this.selectedMids.add(mid)); } @@ -83,24 +280,7 @@ export default class ChatSelection { input.checked = !found; this.toggleSelection(); - if(found) { - bubble.classList.add('backwards'); - bubble.dataset.timeout = '' + setTimeout(() => { - delete bubble.dataset.timeout; - bubble.classList.remove('backwards', 'is-selected'); - }, 200); - } else { - bubble.classList.remove('backwards'); - const timeout = bubble.dataset.timeout; - if(timeout !== undefined) { - clearTimeout(+timeout); - } - - bubble.classList.add('is-selected'); - } - } - - public selectMessage(mid: number) { - + this.updateForwardContainer(); + SetTransition(bubble, 'is-selected', !found, 200); } } \ No newline at end of file diff --git a/src/components/popupForward.ts b/src/components/popupForward.ts new file mode 100644 index 00000000..843b76bb --- /dev/null +++ b/src/components/popupForward.ts @@ -0,0 +1,32 @@ +import appImManager from "../lib/appManagers/appImManager"; +import AppSelectPeers from "./appSelectPeers"; +import { PopupElement } from "./popup"; + +export default class PopupForward extends PopupElement { + private selector: AppSelectPeers; + //private scrollable: Scrollable; + + constructor(mids: number[], onSelect?: () => Promise | void) { + super('popup-forward', null, {closable: true, body: true}); + + this.selector = new AppSelectPeers(this.body, async() => { + const peerID = this.selector.getSelected()[0]; + this.closeBtn.click(); + + this.selector = null; + + await (onSelect ? onSelect() || Promise.resolve() : Promise.resolve()); + + appImManager.setPeer(peerID); + appImManager.chatInputC.initMessagesForward(mids.slice()); + }, ['dialogs', 'contacts'], () => { + this.show(); + }, null, 'send', false); + + //this.scrollable = new Scrollable(this.body); + + this.selector.input.placeholder = 'Forward to...'; + this.title.append(this.selector.input); + } + +} \ No newline at end of file diff --git a/src/components/sidebarLeft/tabs/addMembers.ts b/src/components/sidebarLeft/tabs/addMembers.ts index 2366e997..75c2eb99 100644 --- a/src/components/sidebarLeft/tabs/addMembers.ts +++ b/src/components/sidebarLeft/tabs/addMembers.ts @@ -1,5 +1,5 @@ import { SliderTab } from "../../slider"; -import { AppSelectPeers } from "../../appSelectPeers"; +import AppSelectPeers from "../../appSelectPeers"; import { putPreloader } from "../../misc"; import appChatsManager from "../../../lib/appManagers/appChatsManager"; import appSidebarLeft, { AppSidebarLeft } from ".."; diff --git a/src/components/sidebarLeft/tabs/includedChats.ts b/src/components/sidebarLeft/tabs/includedChats.ts index 1eee6fdd..4ad92098 100644 --- a/src/components/sidebarLeft/tabs/includedChats.ts +++ b/src/components/sidebarLeft/tabs/includedChats.ts @@ -1,5 +1,5 @@ import { SliderTab } from "../../slider"; -import { AppSelectPeers } from "../../appSelectPeers"; +import AppSelectPeers from "../../appSelectPeers"; import appSidebarLeft, { AppSidebarLeft } from ".."; import appDialogsManager from "../../../lib/appManagers/appDialogsManager"; import appPeersManager from "../../../lib/appManagers/appPeersManager"; diff --git a/src/components/sidebarRight/index.ts b/src/components/sidebarRight/index.ts index 9a36e9d5..3de8d1e5 100644 --- a/src/components/sidebarRight/index.ts +++ b/src/components/sidebarRight/index.ts @@ -6,14 +6,14 @@ import AppGifsTab from "./tabs/gifs"; import mediaSizes, { ScreenSize } from "../../helpers/mediaSizes"; import AppPrivateSearchTab from "./tabs/search"; import AppSharedMediaTab from "./tabs/sharedMedia"; -import AppForwardTab from "./tabs/forward"; +//import AppForwardTab from "./tabs/forward"; import { MOUNT_CLASS_TO } from "../../lib/mtproto/mtproto_config"; export const RIGHT_COLUMN_ACTIVE_CLASSNAME = 'is-right-column-shown'; const sharedMediaTab = new AppSharedMediaTab(); const searchTab = new AppPrivateSearchTab(); -const forwardTab = new AppForwardTab(); +//const forwardTab = new AppForwardTab(); const stickersTab = new AppStickersTab(); const pollResultsTab = new AppPollResultsTab(); const gifsTab = new AppGifsTab(); @@ -22,15 +22,15 @@ export class AppSidebarRight extends SidebarSlider { public static SLIDERITEMSIDS = { sharedMedia: 0, search: 1, - forward: 2, - stickers: 3, - pollResults: 4, - gifs: 5, + //forward: 2, + stickers: 2, + pollResults: 3, + gifs: 4, }; public sharedMediaTab: AppSharedMediaTab; public searchTab: AppPrivateSearchTab; - public forwardTab: AppForwardTab; + //public forwardTab: AppForwardTab; public stickersTab: AppStickersTab; public pollResultsTab: AppPollResultsTab; public gifsTab: AppGifsTab; @@ -39,7 +39,7 @@ export class AppSidebarRight extends SidebarSlider { super(document.getElementById('column-right') as HTMLElement, { [AppSidebarRight.SLIDERITEMSIDS.sharedMedia]: sharedMediaTab, [AppSidebarRight.SLIDERITEMSIDS.search]: searchTab, - [AppSidebarRight.SLIDERITEMSIDS.forward]: forwardTab, + //[AppSidebarRight.SLIDERITEMSIDS.forward]: forwardTab, [AppSidebarRight.SLIDERITEMSIDS.stickers]: stickersTab, [AppSidebarRight.SLIDERITEMSIDS.pollResults]: pollResultsTab, [AppSidebarRight.SLIDERITEMSIDS.gifs]: gifsTab @@ -49,7 +49,7 @@ export class AppSidebarRight extends SidebarSlider { this.sharedMediaTab = sharedMediaTab; this.searchTab = searchTab; - this.forwardTab = forwardTab; + //this.forwardTab = forwardTab; this.stickersTab = stickersTab; this.pollResultsTab = pollResultsTab; this.gifsTab = gifsTab; diff --git a/src/components/sidebarRight/tabs/forward.ts b/src/components/sidebarRight/tabs/forward.ts index 689e95d3..2bbbe58a 100644 --- a/src/components/sidebarRight/tabs/forward.ts +++ b/src/components/sidebarRight/tabs/forward.ts @@ -1,6 +1,6 @@ import appSidebarRight, { AppSidebarRight } from ".."; import appMessagesManager from "../../../lib/appManagers/appMessagesManager"; -import { AppSelectPeers } from "../../appSelectPeers"; +import AppSelectPeers from "../../appSelectPeers"; import { putPreloader } from "../../misc"; import { SliderTab } from "../../slider"; @@ -75,7 +75,9 @@ export default class AppForwardTab implements SliderTab { this.sendBtn.classList.toggle('is-visible', !!length); }, ['dialogs', 'contacts'], () => { //console.log('forward rendered:', this.container.querySelector('.selector ul').childElementCount); - appSidebarRight.selectTab(AppSidebarRight.SLIDERITEMSIDS.forward); + + // !!!!!!!!!! UNCOMMENT BELOW IF NEED TO USE THIS CLASS + ////////////////////////////////////////appSidebarRight.selectTab(AppSidebarRight.SLIDERITEMSIDS.forward); appSidebarRight.toggleSidebar(true).then(() => { if(this.selector) { this.selector.checkForTriggers(); diff --git a/src/index.hbs b/src/index.hbs index 48ea9c79..e001c431 100644 --- a/src/index.hbs +++ b/src/index.hbs @@ -587,13 +587,6 @@
-