|
|
|
@ -11,7 +11,7 @@ import apiManager from "../../lib/mtproto/mtprotoworker";
@@ -11,7 +11,7 @@ import apiManager from "../../lib/mtproto/mtprotoworker";
|
|
|
|
|
import opusDecodeController from "../../lib/opusDecodeController"; |
|
|
|
|
import { RichTextProcessor } from "../../lib/richtextprocessor"; |
|
|
|
|
import rootScope from '../../lib/rootScope'; |
|
|
|
|
import { cancelEvent, findUpClassName, getRichValue, isInputEmpty, placeCaretAtEnd, serializeNodes } from "../../helpers/dom"; |
|
|
|
|
import { cancelEvent, CLICK_EVENT_NAME, findUpClassName, getRichValue, isInputEmpty, placeCaretAtEnd, serializeNodes } from "../../helpers/dom"; |
|
|
|
|
import ButtonMenu, { ButtonMenuItemOptions } from '../buttonMenu'; |
|
|
|
|
import emoticonsDropdown from "../emoticonsDropdown"; |
|
|
|
|
import PopupCreatePoll from "../popupCreatePoll"; |
|
|
|
@ -72,27 +72,28 @@ export class ChatInput {
@@ -72,27 +72,28 @@ export class ChatInput {
|
|
|
|
|
|
|
|
|
|
private helperType: Exclude<ChatInputHelperType, 'webpage'>; |
|
|
|
|
private helperFunc: () => void; |
|
|
|
|
private helperWaitingForward: boolean; |
|
|
|
|
|
|
|
|
|
constructor() { |
|
|
|
|
const messageInputField = InputField({ |
|
|
|
|
placeholder: 'Message', |
|
|
|
|
name: 'message' |
|
|
|
|
}); |
|
|
|
|
private willAttachType: 'document' | 'media'; |
|
|
|
|
|
|
|
|
|
messageInputField.input.className = ''; |
|
|
|
|
this.inputScroll.container.append(messageInputField.input); |
|
|
|
|
this.messageInput = messageInputField.input; |
|
|
|
|
private lockRedo = false; |
|
|
|
|
private canRedoFromHTML = ''; |
|
|
|
|
readonly undoHistory: string[] = []; |
|
|
|
|
readonly executedHistory: string[] = []; |
|
|
|
|
private canUndoFromHTML = ''; |
|
|
|
|
|
|
|
|
|
constructor() { |
|
|
|
|
this.attachMessageInputField(); |
|
|
|
|
|
|
|
|
|
this.attachMenu = document.getElementById('attach-file') as HTMLButtonElement; |
|
|
|
|
|
|
|
|
|
let willAttachType: 'document' | 'media'; |
|
|
|
|
this.attachMenuButtons = [{ |
|
|
|
|
icon: 'photo', |
|
|
|
|
text: 'Photo or Video', |
|
|
|
|
onClick: () => { |
|
|
|
|
this.fileInput.value = ''; |
|
|
|
|
this.fileInput.setAttribute('accept', 'image/*, video/*'); |
|
|
|
|
willAttachType = 'media'; |
|
|
|
|
this.willAttachType = 'media'; |
|
|
|
|
this.fileInput.click(); |
|
|
|
|
}, |
|
|
|
|
verify: (peerID: number) => peerID > 0 || appChatsManager.hasRights(peerID, 'send', 'send_media') |
|
|
|
@ -102,7 +103,7 @@ export class ChatInput {
@@ -102,7 +103,7 @@ export class ChatInput {
|
|
|
|
|
onClick: () => { |
|
|
|
|
this.fileInput.value = ''; |
|
|
|
|
this.fileInput.removeAttribute('accept'); |
|
|
|
|
willAttachType = 'document'; |
|
|
|
|
this.willAttachType = 'document'; |
|
|
|
|
this.fileInput.click(); |
|
|
|
|
}, |
|
|
|
|
verify: (peerID: number) => peerID > 0 || appChatsManager.hasRights(peerID, 'send', 'send_media') |
|
|
|
@ -164,6 +165,125 @@ export class ChatInput {
@@ -164,6 +165,125 @@ export class ChatInput {
|
|
|
|
|
this.updateSendBtn(); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
this.fileInput.addEventListener('change', (e) => { |
|
|
|
|
let files = (e.target as HTMLInputElement & EventTarget).files; |
|
|
|
|
if(!files.length) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
new PopupNewMedia(Array.from(files).slice(), this.willAttachType); |
|
|
|
|
this.fileInput.value = ''; |
|
|
|
|
}, false); |
|
|
|
|
|
|
|
|
|
document.addEventListener('paste', this.onDocumentPaste, true); |
|
|
|
|
|
|
|
|
|
this.btnSend.addEventListener(CLICK_EVENT_NAME, this.onBtnSendClick); |
|
|
|
|
|
|
|
|
|
if(this.recorder) { |
|
|
|
|
const onCancelRecordClick = (e: Event) => { |
|
|
|
|
cancelEvent(e); |
|
|
|
|
this.recordCanceled = true; |
|
|
|
|
this.recorder.stop(); |
|
|
|
|
opusDecodeController.setKeepAlive(false); |
|
|
|
|
}; |
|
|
|
|
this.btnCancelRecord.addEventListener(CLICK_EVENT_NAME, onCancelRecordClick); |
|
|
|
|
|
|
|
|
|
this.recorder.onstop = () => { |
|
|
|
|
this.recording = false; |
|
|
|
|
this.chatInput.classList.remove('is-recording', 'is-locked'); |
|
|
|
|
this.updateSendBtn(); |
|
|
|
|
this.recordRippleEl.style.transform = ''; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
this.recorder.ondataavailable = (typedArray: Uint8Array) => { |
|
|
|
|
if(this.recordCanceled) return; |
|
|
|
|
|
|
|
|
|
const duration = (Date.now() - this.recordStartTime) / 1000 | 0; |
|
|
|
|
const dataBlob = new Blob([typedArray], {type: 'audio/ogg'}); |
|
|
|
|
/* const fileName = new Date().toISOString() + ".opus"; |
|
|
|
|
console.log('Recorder data received', typedArray, dataBlob); */ |
|
|
|
|
|
|
|
|
|
/* var url = URL.createObjectURL( dataBlob ); |
|
|
|
|
|
|
|
|
|
var audio = document.createElement('audio'); |
|
|
|
|
audio.controls = true; |
|
|
|
|
audio.src = url; |
|
|
|
|
|
|
|
|
|
var link = document.createElement('a'); |
|
|
|
|
link.href = url; |
|
|
|
|
link.download = fileName; |
|
|
|
|
link.innerHTML = link.download; |
|
|
|
|
|
|
|
|
|
var li = document.createElement('li'); |
|
|
|
|
li.appendChild(link); |
|
|
|
|
li.appendChild(audio); |
|
|
|
|
|
|
|
|
|
document.body.append(li); |
|
|
|
|
|
|
|
|
|
return; */ |
|
|
|
|
|
|
|
|
|
//let perf = performance.now();
|
|
|
|
|
opusDecodeController.decode(typedArray, true).then(result => { |
|
|
|
|
//console.log('WAVEFORM!:', /* waveform, */performance.now() - perf);
|
|
|
|
|
|
|
|
|
|
opusDecodeController.setKeepAlive(false); |
|
|
|
|
|
|
|
|
|
let peerID = appImManager.peerID; |
|
|
|
|
// тут objectURL ставится уже с audio/wav
|
|
|
|
|
appMessagesManager.sendFile(peerID, dataBlob, { |
|
|
|
|
isVoiceMessage: true, |
|
|
|
|
isMedia: true, |
|
|
|
|
duration, |
|
|
|
|
waveform: result.waveform, |
|
|
|
|
objectURL: result.url, |
|
|
|
|
replyToMsgID: this.replyToMsgID |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
this.onMessageSent(false, true); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
/* const url = URL.createObjectURL(dataBlob); |
|
|
|
|
|
|
|
|
|
var audio = document.createElement('audio'); |
|
|
|
|
audio.controls = true; |
|
|
|
|
audio.src = url; |
|
|
|
|
|
|
|
|
|
var link = document.createElement('a'); |
|
|
|
|
link.href = url; |
|
|
|
|
link.download = fileName; |
|
|
|
|
link.innerHTML = link.download; |
|
|
|
|
|
|
|
|
|
var li = document.createElement('li'); |
|
|
|
|
li.appendChild(link); |
|
|
|
|
li.appendChild(audio); |
|
|
|
|
|
|
|
|
|
recordingslist.appendChild(li); */ |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.replyElements.cancelBtn.addEventListener(CLICK_EVENT_NAME, this.onHelperCancel); |
|
|
|
|
this.replyElements.container.addEventListener(CLICK_EVENT_NAME, this.onHelperClick); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private attachMessageInputField() { |
|
|
|
|
const messageInputField = InputField({ |
|
|
|
|
placeholder: 'Message', |
|
|
|
|
name: 'message' |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
messageInputField.input.className = ''; |
|
|
|
|
this.messageInput = messageInputField.input; |
|
|
|
|
this.attachMessageInputListeners(); |
|
|
|
|
|
|
|
|
|
const container = this.inputScroll.container; |
|
|
|
|
if(container.firstElementChild) { |
|
|
|
|
container.replaceChild(messageInputField.input, container.firstElementChild); |
|
|
|
|
} else { |
|
|
|
|
container.append(messageInputField.input); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private attachMessageInputListeners() { |
|
|
|
|
this.messageInput.addEventListener('keydown', (e: KeyboardEvent) => { |
|
|
|
|
if(e.key == 'Enter' && !isTouchSupported) { |
|
|
|
|
/* if(e.ctrlKey || e.metaKey) { |
|
|
|
@ -177,6 +297,8 @@ export class ChatInput {
@@ -177,6 +297,8 @@ export class ChatInput {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.sendMessage(); |
|
|
|
|
} else if(e.ctrlKey || e.metaKey) { |
|
|
|
|
this.handleMarkdownShortcut(e); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
@ -191,13 +313,214 @@ export class ChatInput {
@@ -191,13 +313,214 @@ export class ChatInput {
|
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.messageInput.addEventListener('input', (e) => { |
|
|
|
|
this.messageInput.addEventListener('input', this.onMessageInput); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private onDocumentPaste = (e: ClipboardEvent) => { |
|
|
|
|
const peerID = rootScope.selectedPeerID; |
|
|
|
|
if(!peerID || rootScope.overlayIsActive || (peerID < 0 && !appChatsManager.hasRights(peerID, 'send', 'send_media'))) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//console.log('document paste');
|
|
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
const items = (e.clipboardData || e.originalEvent.clipboardData).items; |
|
|
|
|
//console.log('item', event.clipboardData.getData());
|
|
|
|
|
//let foundFile = false;
|
|
|
|
|
for(let i = 0; i < items.length; ++i) { |
|
|
|
|
if(items[i].kind == 'file') { |
|
|
|
|
e.preventDefault() |
|
|
|
|
e.cancelBubble = true; |
|
|
|
|
e.stopPropagation(); |
|
|
|
|
//foundFile = true;
|
|
|
|
|
|
|
|
|
|
let file = items[i].getAsFile(); |
|
|
|
|
//console.log(items[i], file);
|
|
|
|
|
if(!file) continue; |
|
|
|
|
|
|
|
|
|
this.willAttachType = file.type.indexOf('image/') === 0 ? 'media' : "document"; |
|
|
|
|
new PopupNewMedia([file], this.willAttachType); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
private prepareDocumentExecute = () => { |
|
|
|
|
this.executedHistory.push(this.messageInput.innerHTML); |
|
|
|
|
return () => this.canUndoFromHTML = this.messageInput.innerHTML; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
private undoRedo = (e: Event, type: 'undo' | 'redo', needHTML: string) => { |
|
|
|
|
cancelEvent(e); // cancel legacy event
|
|
|
|
|
|
|
|
|
|
let html = this.messageInput.innerHTML; |
|
|
|
|
if(html && html != needHTML) { |
|
|
|
|
this.lockRedo = true; |
|
|
|
|
|
|
|
|
|
let sameHTMLTimes = 0; |
|
|
|
|
do { |
|
|
|
|
document.execCommand(type, false, null); |
|
|
|
|
const currentHTML = this.messageInput.innerHTML; |
|
|
|
|
if(html == currentHTML) { |
|
|
|
|
if(++sameHTMLTimes > 2) { // * unlink, removeFormat (а может и нет, случай: заболдить подчёркнутый текст (выделить ровно его), попробовать отменить)
|
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
sameHTMLTimes = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
html = currentHTML; |
|
|
|
|
} while(html != needHTML); |
|
|
|
|
|
|
|
|
|
this.lockRedo = false; |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
private handleMarkdownShortcut = (e: KeyboardEvent) => { |
|
|
|
|
const formatKeys: {[key: string]: string | (() => void)} = { |
|
|
|
|
'B': 'Bold', |
|
|
|
|
'I': 'Italic', |
|
|
|
|
'U': 'Underline', |
|
|
|
|
'S': 'Strikethrough', |
|
|
|
|
'M': () => document.execCommand('fontName', false, 'monospace') |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
for(const key in formatKeys) { |
|
|
|
|
const good = e.code == ('Key' + key); |
|
|
|
|
if(good) { |
|
|
|
|
const getSelectedNodes = () => { |
|
|
|
|
const nodes: Node[] = []; |
|
|
|
|
const selection = window.getSelection(); |
|
|
|
|
for(let i = 0; i < selection.rangeCount; ++i) { |
|
|
|
|
const range = selection.getRangeAt(i); |
|
|
|
|
let {startContainer, endContainer} = range; |
|
|
|
|
if(endContainer.nodeType != 3) endContainer = endContainer.firstChild; |
|
|
|
|
|
|
|
|
|
while(startContainer && startContainer != endContainer) { |
|
|
|
|
nodes.push(startContainer.nodeType == 3 ? startContainer : startContainer.firstChild); |
|
|
|
|
startContainer = startContainer.nextSibling; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if(nodes[nodes.length - 1] != endContainer) { |
|
|
|
|
nodes.push(endContainer); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// * filter null's due to <br>
|
|
|
|
|
return nodes.filter(node => !!node); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const saveExecuted = this.prepareDocumentExecute(); |
|
|
|
|
const executed: any[] = []; |
|
|
|
|
/** |
|
|
|
|
* * clear previous formatting, due to Telegram's inability to handle several entities |
|
|
|
|
*/ |
|
|
|
|
const checkForSingle = () => { |
|
|
|
|
const nodes = getSelectedNodes(); |
|
|
|
|
console.log('Using formatting:', formatKeys[key], nodes, this.executedHistory); |
|
|
|
|
|
|
|
|
|
const parents = [...new Set(nodes.map(node => node.parentNode))]; |
|
|
|
|
//const differentParents = !!nodes.find(node => node.parentNode != firstParent);
|
|
|
|
|
const differentParents = parents.length > 1; |
|
|
|
|
|
|
|
|
|
let notSingle = false; |
|
|
|
|
if(differentParents) { |
|
|
|
|
notSingle = true; |
|
|
|
|
} else { |
|
|
|
|
const node = nodes[0]; |
|
|
|
|
if(node && (node.parentNode as HTMLElement) != this.messageInput && (node.parentNode.parentNode as HTMLElement) != this.messageInput) { |
|
|
|
|
notSingle = true; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if(notSingle) { |
|
|
|
|
if(key == 'M') { |
|
|
|
|
executed.push(document.execCommand('styleWithCSS', false, 'true')); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
executed.push(document.execCommand('unlink', false, null)); |
|
|
|
|
executed.push(document.execCommand('removeFormat', false, null)); |
|
|
|
|
// @ts-ignore
|
|
|
|
|
executed.push(typeof(formatKeys[key]) === 'function' ? formatKeys[key]() : document.execCommand(formatKeys[key], false, null)); |
|
|
|
|
|
|
|
|
|
if(key == 'M') { |
|
|
|
|
executed.push(document.execCommand('styleWithCSS', false, 'false')); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
if(key == 'M') { |
|
|
|
|
let haveMonospace = false; |
|
|
|
|
executed.push(document.execCommand('styleWithCSS', false, 'true')); |
|
|
|
|
|
|
|
|
|
const selection = window.getSelection(); |
|
|
|
|
if(!selection.isCollapsed) { |
|
|
|
|
const range = selection.getRangeAt(0); |
|
|
|
|
// @ts-ignore
|
|
|
|
|
if(range.commonAncestorContainer.parentNode.tagName == 'SPAN' || range.commonAncestorContainer.tagName == 'SPAN') { |
|
|
|
|
haveMonospace = true; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
executed.push(document.execCommand('removeFormat', false, null)); |
|
|
|
|
|
|
|
|
|
if(!haveMonospace) { |
|
|
|
|
// @ts-ignore
|
|
|
|
|
executed.push(typeof(formatKeys[key]) === 'function' ? formatKeys[key]() : document.execCommand(formatKeys[key], false, null)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
executed.push(document.execCommand('styleWithCSS', false, 'false')); |
|
|
|
|
} else { |
|
|
|
|
// @ts-ignore
|
|
|
|
|
executed.push(typeof(formatKeys[key]) === 'function' ? formatKeys[key]() : document.execCommand(formatKeys[key], false, null)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
checkForSingle(); |
|
|
|
|
saveExecuted(); |
|
|
|
|
cancelEvent(e); // cancel legacy event
|
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//return;
|
|
|
|
|
if(e.code == 'KeyZ') { |
|
|
|
|
const html = this.messageInput.innerHTML; |
|
|
|
|
|
|
|
|
|
if(e.shiftKey) { |
|
|
|
|
if(this.undoHistory.length) { |
|
|
|
|
this.executedHistory.push(this.messageInput.innerHTML); |
|
|
|
|
const html = this.undoHistory.pop(); |
|
|
|
|
this.undoRedo(e, 'redo', html); |
|
|
|
|
this.canRedoFromHTML = this.undoHistory.length ? this.messageInput.innerHTML : ''; |
|
|
|
|
this.canUndoFromHTML = this.messageInput.innerHTML; |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
// * подождём, когда пользователь сам восстановит поле до нужного состояния, которое стало сразу после saveExecuted
|
|
|
|
|
if(this.executedHistory.length && (!this.canUndoFromHTML || html == this.canUndoFromHTML)) { |
|
|
|
|
this.undoHistory.push(this.messageInput.innerHTML); |
|
|
|
|
const html = this.executedHistory.pop(); |
|
|
|
|
this.undoRedo(e, 'undo', html); |
|
|
|
|
|
|
|
|
|
// * поставим новое состояние чтобы снова подождать, если пользователь изменит что-то, и потом попробует откатить до предыдущего состояния
|
|
|
|
|
this.canUndoFromHTML = this.canRedoFromHTML = this.messageInput.innerHTML; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
private onMessageInput = (/* e: Event */) => { |
|
|
|
|
//console.log('messageInput input', this.messageInput.innerText, this.serializeNodes(Array.from(this.messageInput.childNodes)));
|
|
|
|
|
const value = this.messageInput.innerText; |
|
|
|
|
|
|
|
|
|
const entities = RichTextProcessor.parseEntities(value); |
|
|
|
|
//console.log('messageInput entities', entities);
|
|
|
|
|
|
|
|
|
|
const html = this.messageInput.innerHTML; |
|
|
|
|
if(this.canRedoFromHTML && html != this.canRedoFromHTML && !this.lockRedo) { |
|
|
|
|
this.canRedoFromHTML = ''; |
|
|
|
|
this.undoHistory.length = 0; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const urlEntities = entities.filter(e => e._ == 'messageEntityUrl'); |
|
|
|
|
if(urlEntities.length) { |
|
|
|
|
const richEntities: MessageEntity[] = []; |
|
|
|
@ -259,75 +582,9 @@ export class ChatInput {
@@ -259,75 +582,9 @@ export class ChatInput {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
this.updateSendBtn(); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
/* if(!RichTextProcessor.emojiSupported) { |
|
|
|
|
this.messageInput.addEventListener('copy', (e) => { |
|
|
|
|
const selection = document.getSelection(); |
|
|
|
|
|
|
|
|
|
let range = selection.getRangeAt(0); |
|
|
|
|
let ancestorContainer = range.commonAncestorContainer; |
|
|
|
|
|
|
|
|
|
let str = ''; |
|
|
|
|
|
|
|
|
|
let selectedNodes = Array.from(ancestorContainer.childNodes).slice(range.startOffset, range.endOffset); |
|
|
|
|
if(selectedNodes.length) { |
|
|
|
|
str = serializeNodes(selectedNodes); |
|
|
|
|
} else { |
|
|
|
|
str = selection.toString(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//console.log('messageInput copy', str, ancestorContainer.childNodes, range);
|
|
|
|
|
|
|
|
|
|
//let str = getRichValueWithCaret(this.messageInput);
|
|
|
|
|
//console.log('messageInput childNode copy:', str);
|
|
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
event.clipboardData.setData('text/plain', str); |
|
|
|
|
event.preventDefault(); |
|
|
|
|
}); |
|
|
|
|
} */ |
|
|
|
|
|
|
|
|
|
this.fileInput.addEventListener('change', (e) => { |
|
|
|
|
let files = (e.target as HTMLInputElement & EventTarget).files; |
|
|
|
|
if(!files.length) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
new PopupNewMedia(Array.from(files).slice(), willAttachType); |
|
|
|
|
this.fileInput.value = ''; |
|
|
|
|
}, false); |
|
|
|
|
|
|
|
|
|
document.addEventListener('paste', (e) => { |
|
|
|
|
const peerID = rootScope.selectedPeerID; |
|
|
|
|
if(!peerID || rootScope.overlayIsActive || (peerID < 0 && !appChatsManager.hasRights(peerID, 'send', 'send_media'))) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
//console.log('document paste');
|
|
|
|
|
|
|
|
|
|
// @ts-ignore
|
|
|
|
|
const items = (e.clipboardData || e.originalEvent.clipboardData).items; |
|
|
|
|
//console.log('item', event.clipboardData.getData());
|
|
|
|
|
let foundFile = false; |
|
|
|
|
for(let i = 0; i < items.length; ++i) { |
|
|
|
|
if(items[i].kind == 'file') { |
|
|
|
|
e.preventDefault() |
|
|
|
|
e.cancelBubble = true; |
|
|
|
|
e.stopPropagation(); |
|
|
|
|
foundFile = true; |
|
|
|
|
|
|
|
|
|
let file = items[i].getAsFile(); |
|
|
|
|
//console.log(items[i], file);
|
|
|
|
|
if(!file) continue; |
|
|
|
|
|
|
|
|
|
willAttachType = file.type.indexOf('image/') === 0 ? 'media' : "document"; |
|
|
|
|
new PopupNewMedia([file], willAttachType); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
}, true); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const onBtnSendClick = (e: Event) => { |
|
|
|
|
private onBtnSendClick = (e: Event) => { |
|
|
|
|
cancelEvent(e); |
|
|
|
|
|
|
|
|
|
if(!this.recorder || this.recording || !this.isInputEmpty() || this.forwardingMids.length) { |
|
|
|
@ -417,93 +674,7 @@ export class ChatInput {
@@ -417,93 +674,7 @@ export class ChatInput {
|
|
|
|
|
} |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
this.btnSend.addEventListener('touchend', onBtnSendClick); |
|
|
|
|
this.btnSend.addEventListener('click', onBtnSendClick); |
|
|
|
|
|
|
|
|
|
if(this.recorder) { |
|
|
|
|
const onCancelRecordClick = (e: Event) => { |
|
|
|
|
cancelEvent(e); |
|
|
|
|
this.recordCanceled = true; |
|
|
|
|
this.recorder.stop(); |
|
|
|
|
opusDecodeController.setKeepAlive(false); |
|
|
|
|
}; |
|
|
|
|
this.btnCancelRecord.addEventListener('touchend', onCancelRecordClick); |
|
|
|
|
this.btnCancelRecord.addEventListener('click', onCancelRecordClick); |
|
|
|
|
|
|
|
|
|
this.recorder.onstop = () => { |
|
|
|
|
this.recording = false; |
|
|
|
|
this.chatInput.classList.remove('is-recording', 'is-locked'); |
|
|
|
|
this.updateSendBtn(); |
|
|
|
|
this.recordRippleEl.style.transform = ''; |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
this.recorder.ondataavailable = (typedArray: Uint8Array) => { |
|
|
|
|
if(this.recordCanceled) return; |
|
|
|
|
|
|
|
|
|
const duration = (Date.now() - this.recordStartTime) / 1000 | 0; |
|
|
|
|
const dataBlob = new Blob([typedArray], {type: 'audio/ogg'}); |
|
|
|
|
/* const fileName = new Date().toISOString() + ".opus"; |
|
|
|
|
console.log('Recorder data received', typedArray, dataBlob); */ |
|
|
|
|
|
|
|
|
|
/* var url = URL.createObjectURL( dataBlob ); |
|
|
|
|
|
|
|
|
|
var audio = document.createElement('audio'); |
|
|
|
|
audio.controls = true; |
|
|
|
|
audio.src = url; |
|
|
|
|
|
|
|
|
|
var link = document.createElement('a'); |
|
|
|
|
link.href = url; |
|
|
|
|
link.download = fileName; |
|
|
|
|
link.innerHTML = link.download; |
|
|
|
|
|
|
|
|
|
var li = document.createElement('li'); |
|
|
|
|
li.appendChild(link); |
|
|
|
|
li.appendChild(audio); |
|
|
|
|
|
|
|
|
|
document.body.append(li); |
|
|
|
|
|
|
|
|
|
return; */ |
|
|
|
|
|
|
|
|
|
//let perf = performance.now();
|
|
|
|
|
opusDecodeController.decode(typedArray, true).then(result => { |
|
|
|
|
//console.log('WAVEFORM!:', /* waveform, */performance.now() - perf);
|
|
|
|
|
|
|
|
|
|
opusDecodeController.setKeepAlive(false); |
|
|
|
|
|
|
|
|
|
let peerID = appImManager.peerID; |
|
|
|
|
// тут objectURL ставится уже с audio/wav
|
|
|
|
|
appMessagesManager.sendFile(peerID, dataBlob, { |
|
|
|
|
isVoiceMessage: true, |
|
|
|
|
isMedia: true, |
|
|
|
|
duration, |
|
|
|
|
waveform: result.waveform, |
|
|
|
|
objectURL: result.url, |
|
|
|
|
replyToMsgID: this.replyToMsgID |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
this.onMessageSent(false, true); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
/* const url = URL.createObjectURL(dataBlob); |
|
|
|
|
|
|
|
|
|
var audio = document.createElement('audio'); |
|
|
|
|
audio.controls = true; |
|
|
|
|
audio.src = url; |
|
|
|
|
|
|
|
|
|
var link = document.createElement('a'); |
|
|
|
|
link.href = url; |
|
|
|
|
link.download = fileName; |
|
|
|
|
link.innerHTML = link.download; |
|
|
|
|
|
|
|
|
|
var li = document.createElement('li'); |
|
|
|
|
li.appendChild(link); |
|
|
|
|
li.appendChild(audio); |
|
|
|
|
|
|
|
|
|
recordingslist.appendChild(li); */ |
|
|
|
|
}; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const onCancelHelper = (e: Event) => { |
|
|
|
|
private onHelperCancel = (e: Event) => { |
|
|
|
|
cancelEvent(e); |
|
|
|
|
|
|
|
|
|
if(this.willSendWebPage) { |
|
|
|
@ -523,16 +694,13 @@ export class ChatInput {
@@ -523,16 +694,13 @@ export class ChatInput {
|
|
|
|
|
this.updateSendBtn(); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
this.replyElements.cancelBtn.addEventListener(isTouchSupported ? 'touchend' : 'click', onCancelHelper); |
|
|
|
|
|
|
|
|
|
let d = false; |
|
|
|
|
this.replyElements.container.addEventListener(isTouchSupported ? 'touchend' : 'click', (e) => { |
|
|
|
|
private onHelperClick = (e: Event) => { |
|
|
|
|
cancelEvent(e); |
|
|
|
|
|
|
|
|
|
if(!findUpClassName(e.target, 'reply-wrapper')) return; |
|
|
|
|
if(this.helperType == 'forward') { |
|
|
|
|
if(d) return; |
|
|
|
|
d = true; |
|
|
|
|
if(this.helperWaitingForward) return; |
|
|
|
|
this.helperWaitingForward = true; |
|
|
|
|
|
|
|
|
|
const mids = this.forwardingMids.slice(); |
|
|
|
|
const helperFunc = this.helperFunc; |
|
|
|
@ -541,7 +709,7 @@ export class ChatInput {
@@ -541,7 +709,7 @@ export class ChatInput {
|
|
|
|
|
new PopupForward(mids, () => { |
|
|
|
|
selected = true; |
|
|
|
|
}, () => { |
|
|
|
|
d = false; |
|
|
|
|
this.helperWaitingForward = false; |
|
|
|
|
|
|
|
|
|
if(!selected) { |
|
|
|
|
helperFunc(); |
|
|
|
@ -552,7 +720,16 @@ export class ChatInput {
@@ -552,7 +720,16 @@ export class ChatInput {
|
|
|
|
|
} else if(this.helperType == 'edit') { |
|
|
|
|
appImManager.setPeer(rootScope.selectedPeerID, this.editMsgID); |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
public clearInput() { |
|
|
|
|
this.attachMessageInputField(); |
|
|
|
|
|
|
|
|
|
// clear executions
|
|
|
|
|
this.canRedoFromHTML = ''; |
|
|
|
|
this.undoHistory.length = 0; |
|
|
|
|
this.executedHistory.length = 0; |
|
|
|
|
this.canUndoFromHTML = ''; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public isInputEmpty() { |
|
|
|
@ -579,7 +756,7 @@ export class ChatInput {
@@ -579,7 +756,7 @@ export class ChatInput {
|
|
|
|
|
this.lastUrl = ''; |
|
|
|
|
delete this.noWebPage; |
|
|
|
|
this.willSendWebPage = null; |
|
|
|
|
this.messageInput.innerText = ''; |
|
|
|
|
this.clearInput(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if(clearReply || clearInput) { |
|
|
|
@ -683,7 +860,7 @@ export class ChatInput {
@@ -683,7 +860,7 @@ export class ChatInput {
|
|
|
|
|
|
|
|
|
|
public clearHelper(type?: ChatInputHelperType) { |
|
|
|
|
if(this.helperType == 'edit' && type != 'edit') { |
|
|
|
|
this.messageInput.innerText = ''; |
|
|
|
|
this.clearInput(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if(type) { |
|
|
|
@ -718,7 +895,9 @@ export class ChatInput {
@@ -718,7 +895,9 @@ export class ChatInput {
|
|
|
|
|
} */ |
|
|
|
|
|
|
|
|
|
if(input !== undefined) { |
|
|
|
|
this.clearInput(); |
|
|
|
|
this.messageInput.innerHTML = input || ''; |
|
|
|
|
this.onMessageInput(); |
|
|
|
|
window.requestAnimationFrame(() => { |
|
|
|
|
placeCaretAtEnd(this.messageInput); |
|
|
|
|
this.inputScroll.scrollTop = this.inputScroll.scrollHeight; |
|
|
|
|