morethanwords
4 years ago
15 changed files with 635 additions and 578 deletions
@ -0,0 +1,80 @@
@@ -0,0 +1,80 @@
|
||||
import appImManager from "../../lib/appManagers/appImManager"; |
||||
import appMessagesManager from "../../lib/appManagers/appMessagesManager"; |
||||
import appPeersManager from "../../lib/appManagers/appPeersManager"; |
||||
import { RichTextProcessor } from "../../lib/richtextprocessor"; |
||||
import { cancelEvent, $rootScope } from "../../lib/utils"; |
||||
import appMediaPlaybackController from "../appMediaPlaybackController"; |
||||
import { formatDate } from "../wrappers"; |
||||
|
||||
export class ChatAudio { |
||||
public container: HTMLElement; |
||||
private toggle: HTMLElement; |
||||
private title: HTMLElement; |
||||
private subtitle: HTMLElement; |
||||
private close: HTMLElement; |
||||
|
||||
constructor() { |
||||
this.container = document.createElement('div'); |
||||
this.container.classList.add('pinned-audio', 'pinned-container'); |
||||
this.container.style.display = 'none'; |
||||
|
||||
this.toggle = document.createElement('div'); |
||||
this.toggle.classList.add('pinned-audio-ico', 'tgico'); |
||||
|
||||
this.title = document.createElement('div'); |
||||
this.title.classList.add('pinned-audio-title'); |
||||
|
||||
this.subtitle = document.createElement('div'); |
||||
this.subtitle.classList.add('pinned-audio-subtitle'); |
||||
|
||||
this.close = document.createElement('button'); |
||||
this.close.classList.add('pinned-audio-close', 'btn-icon', 'tgico-close'); |
||||
|
||||
this.container.append(this.toggle, this.title, this.subtitle, this.close); |
||||
|
||||
this.close.addEventListener('click', (e) => { |
||||
cancelEvent(e); |
||||
this.container.style.display = 'none'; |
||||
this.container.parentElement.classList.remove('is-audio-shown'); |
||||
if(this.toggle.classList.contains('flip-icon')) { |
||||
appMediaPlaybackController.toggle(); |
||||
} |
||||
}); |
||||
|
||||
this.toggle.addEventListener('click', (e) => { |
||||
cancelEvent(e); |
||||
appMediaPlaybackController.toggle(); |
||||
}); |
||||
|
||||
$rootScope.$on('audio_play', (e: CustomEvent) => { |
||||
const {doc, mid} = e.detail; |
||||
|
||||
let title: string, subtitle: string; |
||||
if(doc.type == 'voice' || doc.type == 'round') { |
||||
const message = appMessagesManager.getMessage(mid); |
||||
title = appPeersManager.getPeerTitle(message.fromID, false, true); |
||||
//subtitle = 'Voice message';
|
||||
subtitle = formatDate(message.date, false, false); |
||||
} else { |
||||
title = doc.audioTitle || doc.file_name; |
||||
subtitle = doc.audioPerformer ? RichTextProcessor.wrapPlainText(doc.audioPerformer) : 'Unknown Artist'; |
||||
} |
||||
|
||||
this.title.innerHTML = title; |
||||
this.subtitle.innerHTML = subtitle; |
||||
this.toggle.classList.add('flip-icon'); |
||||
|
||||
this.container.dataset.mid = '' + mid; |
||||
if(this.container.style.display) { |
||||
const scrollTop = appImManager.scrollable.scrollTop; |
||||
this.container.style.display = ''; |
||||
this.container.parentElement.classList.add('is-audio-shown'); |
||||
appImManager.scrollable.scrollTop = scrollTop; |
||||
} |
||||
}); |
||||
|
||||
$rootScope.$on('audio_pause', () => { |
||||
this.toggle.classList.remove('flip-icon'); |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,167 @@
@@ -0,0 +1,167 @@
|
||||
import appChatsManager from "../../lib/appManagers/appChatsManager"; |
||||
import appImManager from "../../lib/appManagers/appImManager"; |
||||
import appMessagesManager from "../../lib/appManagers/appMessagesManager"; |
||||
import appPeersManager from "../../lib/appManagers/appPeersManager"; |
||||
import { findUpClassName, $rootScope } from "../../lib/utils"; |
||||
import appForward from "../appForward"; |
||||
import { parseMenuButtonsTo, attachContextMenuListener, positionMenu, openBtnMenu } from "../misc"; |
||||
import { PopupButton, PopupPeer } from "../popup"; |
||||
|
||||
export class ChatContextMenu { |
||||
private element = document.getElementById('bubble-contextmenu') as HTMLDivElement; |
||||
private buttons: { |
||||
reply: HTMLButtonElement, |
||||
edit: HTMLButtonElement, |
||||
copy: HTMLButtonElement, |
||||
pin: HTMLButtonElement, |
||||
forward: HTMLButtonElement, |
||||
delete: HTMLButtonElement |
||||
} = {} as any; |
||||
public msgID: number; |
||||
|
||||
constructor(private attachTo: HTMLElement) { |
||||
parseMenuButtonsTo(this.buttons, this.element.children); |
||||
|
||||
attachContextMenuListener(attachTo, (e) => { |
||||
let bubble: HTMLElement = null; |
||||
|
||||
try { |
||||
bubble = findUpClassName(e.target, 'bubble__container'); |
||||
} catch(e) {} |
||||
|
||||
if(!bubble) return; |
||||
|
||||
if(e instanceof MouseEvent) e.preventDefault(); |
||||
if(this.element.classList.contains('active')) { |
||||
return false; |
||||
} |
||||
if(e instanceof MouseEvent) e.cancelBubble = true; |
||||
|
||||
bubble = bubble.parentElement as HTMLDivElement; // bc container
|
||||
|
||||
let msgID = +bubble.dataset.mid; |
||||
if(!msgID) return; |
||||
|
||||
let peerID = $rootScope.selectedPeerID; |
||||
this.msgID = msgID; |
||||
|
||||
const message = appMessagesManager.getMessage(msgID); |
||||
|
||||
this.buttons.copy.style.display = message.message ? '' : 'none'; |
||||
|
||||
if($rootScope.myID == peerID || (peerID < 0 && appChatsManager.hasRights(-peerID, 'pin'))) { |
||||
this.buttons.pin.style.display = ''; |
||||
} else { |
||||
this.buttons.pin.style.display = 'none'; |
||||
} |
||||
|
||||
this.buttons.edit.style.display = appMessagesManager.canEditMessage(msgID) ? '' : 'none'; |
||||
|
||||
let side: 'left' | 'right' = bubble.classList.contains('is-in') ? 'left' : 'right'; |
||||
positionMenu(e, this.element, side); |
||||
openBtnMenu(this.element); |
||||
|
||||
/////this.log('contextmenu', e, bubble, msgID, side);
|
||||
}); |
||||
|
||||
this.buttons.copy.addEventListener('click', () => { |
||||
let message = appMessagesManager.getMessage(this.msgID); |
||||
|
||||
let str = message ? message.message : ''; |
||||
|
||||
var textArea = document.createElement("textarea"); |
||||
textArea.value = str; |
||||
textArea.style.position = "fixed"; //avoid scrolling to bottom
|
||||
document.body.appendChild(textArea); |
||||
textArea.focus(); |
||||
textArea.select(); |
||||
|
||||
try { |
||||
document.execCommand('copy'); |
||||
} catch (err) { |
||||
console.error('Oops, unable to copy', err); |
||||
} |
||||
|
||||
document.body.removeChild(textArea); |
||||
}); |
||||
|
||||
this.buttons.delete.addEventListener('click', () => { |
||||
let peerID = $rootScope.selectedPeerID; |
||||
let firstName = appPeersManager.getPeerTitle(peerID, false, true); |
||||
|
||||
let callback = (revoke: boolean) => { |
||||
appMessagesManager.deleteMessages([this.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 |
||||
}); |
||||
|
||||
let popup = new PopupPeer('popup-delete-chat', { |
||||
peerID: peerID, |
||||
title: title, |
||||
description: description, |
||||
buttons: buttons |
||||
}); |
||||
|
||||
popup.show(); |
||||
}); |
||||
|
||||
this.buttons.reply.addEventListener('click', () => { |
||||
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; |
||||
}); |
||||
|
||||
this.buttons.forward.addEventListener('click', () => { |
||||
appForward.init([this.msgID]); |
||||
}); |
||||
|
||||
this.buttons.edit.addEventListener('click', () => { |
||||
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; |
||||
}); |
||||
|
||||
this.buttons.pin.addEventListener('click', () => { |
||||
appMessagesManager.updatePinnedMessage($rootScope.selectedPeerID, this.msgID); |
||||
}); |
||||
} |
||||
} |
@ -0,0 +1,187 @@
@@ -0,0 +1,187 @@
|
||||
import appImManager from "../../lib/appManagers/appImManager"; |
||||
import { $rootScope, cancelEvent, whichChild, findUpTag } from "../../lib/utils"; |
||||
import AppSearch, { SearchGroup } from "../appSearch"; |
||||
import PopupDatePicker from "../popupDatepicker"; |
||||
import { ripple } from "../ripple"; |
||||
import SearchInput from "../searchInput"; |
||||
|
||||
export class ChatSearch { |
||||
private element: HTMLElement; |
||||
private backBtn: HTMLElement; |
||||
private searchInput: SearchInput; |
||||
|
||||
private results: HTMLElement; |
||||
|
||||
private footer: HTMLElement; |
||||
private dateBtn: HTMLElement; |
||||
private foundCountEl: HTMLElement; |
||||
private controls: HTMLElement; |
||||
private downBtn: HTMLElement; |
||||
private upBtn: HTMLElement; |
||||
|
||||
private appSearch: AppSearch; |
||||
private searchGroup: SearchGroup; |
||||
|
||||
private foundCount = 0; |
||||
private selectedIndex = 0; |
||||
private setPeerPromise: Promise<any>; |
||||
|
||||
constructor() { |
||||
this.element = document.createElement('div'); |
||||
this.element.classList.add('sidebar-header', 'chat-search', 'chats-container'); |
||||
|
||||
this.backBtn = document.createElement('button'); |
||||
this.backBtn.classList.add('btn-icon', 'tgico-back', 'sidebar-close-button'); |
||||
ripple(this.backBtn); |
||||
|
||||
this.backBtn.addEventListener('click', () => { |
||||
appImManager.topbar.classList.remove('hide-pinned'); |
||||
this.element.remove(); |
||||
this.searchInput.remove(); |
||||
this.results.remove(); |
||||
this.footer.remove(); |
||||
this.footer.removeEventListener('click', this.onFooterClick); |
||||
this.dateBtn.removeEventListener('click', this.onDateClick); |
||||
this.upBtn.removeEventListener('click', this.onUpClick); |
||||
this.downBtn.removeEventListener('click', this.onDownClick); |
||||
this.searchGroup.list.removeEventListener('click', this.onResultsClick); |
||||
appImManager.bubblesContainer.classList.remove('search-results-active'); |
||||
}, {once: true}); |
||||
|
||||
this.searchInput = new SearchInput('Search'); |
||||
|
||||
// Results
|
||||
this.results = document.createElement('div'); |
||||
this.results.classList.add('chat-search-results', 'chats-container'); |
||||
|
||||
this.searchGroup = new SearchGroup('', 'messages', undefined, '', false); |
||||
this.searchGroup.list.addEventListener('click', this.onResultsClick); |
||||
|
||||
this.appSearch = new AppSearch(this.results, this.searchInput, { |
||||
messages: this.searchGroup |
||||
}, (count) => { |
||||
this.foundCount = count; |
||||
|
||||
if(!this.foundCount) { |
||||
this.foundCountEl.innerText = this.searchInput.value ? 'No results' : ''; |
||||
this.results.classList.remove('active'); |
||||
appImManager.bubblesContainer.classList.remove('search-results-active'); |
||||
this.upBtn.setAttribute('disabled', 'true'); |
||||
this.downBtn.setAttribute('disabled', 'true'); |
||||
} else { |
||||
this.selectResult(this.searchGroup.list.children[0] as HTMLElement); |
||||
} |
||||
}); |
||||
this.appSearch.beginSearch($rootScope.selectedPeerID); |
||||
|
||||
//appImManager.topbar.parentElement.insertBefore(this.results, appImManager.bubblesContainer);
|
||||
appImManager.bubblesContainer.append(this.results); |
||||
|
||||
// Footer
|
||||
this.footer = document.createElement('div'); |
||||
this.footer.classList.add('chat-search-footer'); |
||||
|
||||
this.footer.addEventListener('click', this.onFooterClick); |
||||
ripple(this.footer); |
||||
|
||||
this.foundCountEl = document.createElement('span'); |
||||
this.foundCountEl.classList.add('chat-search-count'); |
||||
|
||||
this.dateBtn = document.createElement('button'); |
||||
this.dateBtn.classList.add('btn-icon', 'tgico-calendar'); |
||||
|
||||
this.controls = document.createElement('div'); |
||||
this.controls.classList.add('chat-search-controls'); |
||||
|
||||
this.upBtn = document.createElement('button'); |
||||
this.upBtn.classList.add('btn-icon', 'tgico-up'); |
||||
this.downBtn = document.createElement('button'); |
||||
this.downBtn.classList.add('btn-icon', 'tgico-down'); |
||||
|
||||
this.upBtn.setAttribute('disabled', 'true'); |
||||
this.downBtn.setAttribute('disabled', 'true'); |
||||
|
||||
this.dateBtn.addEventListener('click', this.onDateClick); |
||||
this.upBtn.addEventListener('click', this.onUpClick); |
||||
this.downBtn.addEventListener('click', this.onDownClick); |
||||
this.controls.append(this.upBtn, this.downBtn); |
||||
|
||||
this.footer.append(this.foundCountEl, this.dateBtn, this.controls); |
||||
|
||||
appImManager.topbar.parentElement.insertBefore(this.footer, appImManager.chatInput); |
||||
|
||||
// Append container
|
||||
this.element.append(this.backBtn, this.searchInput.container); |
||||
|
||||
appImManager.topbar.classList.add('hide-pinned'); |
||||
appImManager.topbar.parentElement.append(this.element); |
||||
|
||||
this.searchInput.input.focus(); |
||||
} |
||||
|
||||
onDateClick = (e: MouseEvent) => { |
||||
cancelEvent(e); |
||||
new PopupDatePicker(new Date(), appImManager.onDatePick).show(); |
||||
}; |
||||
|
||||
selectResult = (elem: HTMLElement) => { |
||||
if(this.setPeerPromise) return this.setPeerPromise; |
||||
|
||||
const peerID = +elem.getAttribute('data-peerID'); |
||||
const lastMsgID = +elem.dataset.mid || undefined; |
||||
|
||||
const index = whichChild(elem); |
||||
|
||||
if(index == (this.foundCount - 1)) { |
||||
this.upBtn.setAttribute('disabled', 'true'); |
||||
} else { |
||||
this.upBtn.removeAttribute('disabled'); |
||||
} |
||||
|
||||
if(!index) { |
||||
this.downBtn.setAttribute('disabled', 'true'); |
||||
} else { |
||||
this.downBtn.removeAttribute('disabled'); |
||||
} |
||||
|
||||
this.results.classList.remove('active'); |
||||
appImManager.bubblesContainer.classList.remove('search-results-active'); |
||||
|
||||
const res = appImManager.setPeer(peerID, lastMsgID); |
||||
this.setPeerPromise = (res instanceof Promise ? res : Promise.resolve(res)).then(() => { |
||||
this.selectedIndex = index; |
||||
this.foundCountEl.innerText = `${index + 1} of ${this.foundCount}`; |
||||
|
||||
const renderedCount = this.searchGroup.list.childElementCount; |
||||
if(this.selectedIndex >= (renderedCount - 6)) { |
||||
this.appSearch.searchMore(); |
||||
} |
||||
}).finally(() => { |
||||
this.setPeerPromise = null; |
||||
}); |
||||
}; |
||||
|
||||
onResultsClick = (e: MouseEvent) => { |
||||
const target = findUpTag(e.target, 'LI'); |
||||
if(target) { |
||||
this.selectResult(target); |
||||
} |
||||
}; |
||||
|
||||
onFooterClick = (e: MouseEvent) => { |
||||
if(this.foundCount) { |
||||
appImManager.bubblesContainer.classList.toggle('search-results-active'); |
||||
this.results.classList.toggle('active'); |
||||
} |
||||
}; |
||||
|
||||
onUpClick = (e: MouseEvent) => { |
||||
cancelEvent(e); |
||||
this.selectResult(this.searchGroup.list.children[this.selectedIndex + 1] as HTMLElement); |
||||
}; |
||||
|
||||
onDownClick = (e: MouseEvent) => { |
||||
cancelEvent(e); |
||||
this.selectResult(this.searchGroup.list.children[this.selectedIndex - 1] as HTMLElement); |
||||
}; |
||||
} |
Loading…
Reference in new issue