From 96230a60f59deef5091346c3d6be2151f96af28e Mon Sep 17 00:00:00 2001 From: AlphaX-Projects <77661270+AlphaX-Projects@users.noreply.github.com> Date: Sat, 25 May 2024 12:05:45 +0200 Subject: [PATCH] Q-Chat fixes and additions - Added copy paste with line breaks to message field - Added upload file ( max file size 125 MB ) - Added auto detect if image is GIF and display it so ( max file size 3 MB ) - Added auto detect if file is attachment or file - Added download splash if download larger files - Fixed regex for qortal// links - Fixed rporsemirror whitespace error - Fixed upload attachment like pdf ( max file size 10 MB ) - Fixed delete image, gif, attachment, file --- img/file-icon.png | Bin 0 -> 10219 bytes plugins/plugins/core/components/ChatPage.js | 950 ++++++++++++++++-- .../plugins/core/components/ChatScroller.js | 301 +++++- .../plugins/core/components/ChatTextEditor.js | 38 +- .../plugins/core/components/plugins-css.js | 201 +++- 5 files changed, 1370 insertions(+), 120 deletions(-) create mode 100644 img/file-icon.png diff --git a/img/file-icon.png b/img/file-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..0e12769d369bf2bcc657688c8b91ed7dec5ca255 GIT binary patch literal 10219 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 AlK=n! literal 0 HcmV?d00001 diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 030126b3..b6047770 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -8,6 +8,7 @@ import { escape } from 'html-escaper' import { inputKeyCodes, replaceMessagesEdited, generateIdFromAddresses } from '../../utils/functions' import { publishData, modalHelper, RequestQueue } from '../../utils/classes' import { EmojiPicker } from 'emoji-picker-js' +import { Slice, Fragment, Node } from 'prosemirror-model' import { chatpageStyles } from './plugins-css' import localForage from 'localforage' import StarterKit from '@tiptap/starter-kit' @@ -84,11 +85,17 @@ class ChatPage extends LitElement { editedMessageObj: { type: Object }, iframeHeight: { type: Number }, imageFile: { type: Object }, + gifFile: { type: Object }, attachment: { type: Object }, + appFile: { type: Object }, isUploadingImage: { type: Boolean }, - isDeletingImage: { type: Boolean }, + isUploadingGif: { type: Boolean }, isUploadingAttachment: { type: Boolean }, + isUploadingAppFile: { type: Boolean }, + isDeletingImage: { type: Boolean }, + isDeletingGif: { type: Boolean }, isDeletingAttachment: { type: Boolean }, + isDeletingAppFile: { type: Boolean }, userLanguage: { type: String }, lastMessageRefVisible: { type: Boolean }, isLoadingOldMessages: { type: Boolean }, @@ -172,7 +179,9 @@ class ChatPage extends LitElement { this.editedMessageObj = null this.iframeHeight = 42 this.imageFile = null + this.gifFile = null this.attachment = null + this.appFile = null this.uid = new ShortUniqueId() this.userLanguage = "" this.lastMessageRefVisible = false @@ -391,6 +400,19 @@ class ChatPage extends LitElement { ` : '' } + ${(this.isUploadingGif || this.isDeletingGif) ? + html` +
+
+
+
+

${this.isDeletingImage ? translate("chatpage.cchange104") : translate("chatpage.cchange103")}

+
+
+
+ ` + : '' + } ${(this.isUploadingAttachment || this.isDeletingAttachment) ? html`
@@ -404,9 +426,23 @@ class ChatPage extends LitElement { ` : '' } + ${(this.isUploadingAppFile || this.isDeletingAppFile) ? + html` +
+
+
+
+

${this.isDeletingAppFile ? translate("chatpage.cchange99") : translate("chatpage.cchange98")}

+
+
+
+ ` + : '' + } {this.removeImage();}} style=${(this.imageFile && !this.isUploadingImage) ? "visibility:visible; z-index:50" : "visibility: hidden;z-index:-100"}>
+

${translate("chatpage.cchange110")}

${this.imageFile && html`
+ {this.removeGif();}} style=${(this.gifFile && !this.isUploadingGif) ? "visibility:visible; z-index:50" : "visibility: hidden;z-index:-100"}> +
+
+

${translate("chatpage.cchange111")}

+ ${this.gifFile && + html` + dialog-gif + ` + } +
+ this.updatePlaceholder(editor, value)} + > + +
+ +
+
+
{this.removeAttachment();}} style=${this.attachment && !this.isUploadingAttachment ? "visibility: visible; z-index: 50" : "visibility: hidden; z-index: -100"}>
+

${translate("chatpage.cchange112")}

${this.attachment && html`
attachment-icon
@@ -499,6 +588,50 @@ class ChatPage extends LitElement {
+ {this.removeAppFile();}} style=${this.appFile && !this.isUploadingAppFile ? "visibility: visible; z-index: 50" : "visibility: hidden; z-index: -100"}> +
+
+

${translate("chatpage.cchange113")}

+ ${this.appFile && + html` +
file-icon
+ ` + } +

${this.appFile && this.appFile.name}

+
+ this.updatePlaceholder(editor, value)} + > + +
+ +
+
+

${translate("chatpage.cchange41")}


@@ -834,36 +967,70 @@ class ChatPage extends LitElement { async connectedCallback() { super.connectedCallback() await this.initUpdate() + if (!this.webWorker) { this.webWorker = new WebWorker() } + if (!this.webWorkerFile) { this.webWorkerFile = new WebWorkerFile() } + if (!this.webWorkerSortMessages) { this.webWorkerSortMessages = new WebWorkerSortMessages() - } + if (!this.webWorkerDecodeMessages) { this.webWorkerDecodeMessages = new WebWorkerDecodeMessages() } + await this.getUpdateCompleteTextEditor() const elementChatId = this.shadowRoot.getElementById('_chatEditorDOM').shadowRoot.getElementById('_chatEditorDOM') const elementChatImageId = this.shadowRoot.getElementById('chatTextCaption').shadowRoot.getElementById('newChat') + const elementChatGifId = this.shadowRoot.getElementById('chatGifId').shadowRoot.getElementById('newGifChat') const elementChatAttachmentId = this.shadowRoot.getElementById('chatAttachmentId').shadowRoot.getElementById('newAttachmentChat') + const elementChatFileId = this.shadowRoot.getElementById('chatFileId').shadowRoot.getElementById('newFileChat') + + const placeholderString = get('chatpage.cchange114') + + const clipboardTextParser = (text, context, plain) => { + const splitLines = text.replace().split(/(?:\r\n?|\n)/) + const nodesLines = [] + + splitLines.forEach(line => { + let nodeJson = {type: "paragraph"} + + if (line.length === 0) { + nodeJson.content = [{type: "hardBreak"}] + } else if (line.length > 0) { + nodeJson.content = [{type: "text", text: line}] + } + + let modifiedLine = Node.fromJSON(context.doc.type.schema, nodeJson) + + nodesLines.push(modifiedLine) + }) + + const fragment = Fragment.fromArray(nodesLines) + + return Slice.maxOpen(fragment) + } + this.editor = new Editor({ + editorProps: { + clipboardTextParser: clipboardTextParser + }, onUpdate: () => { this.shadowRoot.getElementById('_chatEditorDOM').getMessageSize(this.editor.getJSON()) }, - element: elementChatId, extensions: [ StarterKit, Underline, Highlight, Placeholder.configure({ - placeholder: 'Write something …', + placeholder: `${placeholderString}` }), Extension.create({ name: 'shortcuts', @@ -901,7 +1068,7 @@ class ChatPage extends LitElement { Underline, Highlight, Placeholder.configure({ - placeholder: 'Write something …', + placeholder: `${placeholderString}` }), Extension.create({ addKeyboardShortcuts: () => { @@ -919,6 +1086,34 @@ class ChatPage extends LitElement { ] }) + this.editorGif = new Editor({ + onUpdate: () => { + this.shadowRoot.getElementById('chatGifId').getMessageSize(this.editorGif.getJSON()) + }, + element: elementChatGifId, + extensions: [ + StarterKit, + Underline, + Highlight, + Placeholder.configure({ + placeholder: `${placeholderString}` + }), + Extension.create({ + addKeyboardShortcuts: () => { + return { + 'Enter': () => { + const chatTextEditor = this.shadowRoot.getElementById('chatGifId') + chatTextEditor.sendMessageFunc({ + type: 'gif' + }) + return true + } + } + } + }) + ] + }) + this.editorAttachment = new Editor({ onUpdate: () => { this.shadowRoot.getElementById('chatAttachmentId').getMessageSize(this.editorAttachment.getJSON()) @@ -929,7 +1124,7 @@ class ChatPage extends LitElement { Underline, Highlight, Placeholder.configure({ - placeholder: 'Write something …', + placeholder: `${placeholderString}` }), Extension.create({ addKeyboardShortcuts: () => { @@ -947,6 +1142,34 @@ class ChatPage extends LitElement { ] }) + this.editorFile = new Editor({ + onUpdate: () => { + this.shadowRoot.getElementById('chatFileId').getMessageSize(this.editorFile.getJSON()) + }, + element: elementChatFileId, + extensions: [ + StarterKit, + Underline, + Highlight, + Placeholder.configure({ + placeholder: `${placeholderString}` + }), + Extension.create({ + addKeyboardShortcuts: () => { + return { + 'Enter': () => { + const chatTextEditor = this.shadowRoot.getElementById('chatFileId') + chatTextEditor.sendMessageFunc({ + type: 'file' + }) + return true + } + } + } + }) + ] + }) + document.addEventListener('keydown', this.initialChat) document.addEventListener('paste', this.pasteImage) @@ -983,25 +1206,44 @@ class ChatPage extends LitElement { disconnectedCallback() { super.disconnectedCallback() + if (this.webSocket) { this.webSocket.close(1000, 'switch chat') this.webSocket = '' } + if (this.webWorker) { this.webWorker.terminate() } + if (this.webWorkerFile) { this.webWorkerFile.terminate() } + if (this.webWorkerSortMessages) { this.webWorkerSortMessages.terminate() } + if (this.editor) { this.editor.destroy() } + if (this.editorImage) { this.editorImage.destroy() } + + if (this.editorGif) { + this.editorGif.destroy() + } + + if (this.editorAttachment) { + this.editorAttachment.destroy() + } + + if (this.editorFile) { + this.editorFile.destroy() + } + if (this.observer) { this.observer.disconnect() } @@ -1023,7 +1265,6 @@ class ChatPage extends LitElement { } async pasteImage(e) { - const event = e const handleTransferIntoURL = (dataTransfer) => { try { const [firstItem] = dataTransfer.items @@ -1031,15 +1272,19 @@ class ChatPage extends LitElement { return blob } catch (error) { /* empty */ } } - if (event.clipboardData) { - const blobFound = handleTransferIntoURL(event.clipboardData) + + if (e.clipboardData) { + const blobFound = handleTransferIntoURL(e.clipboardData) + if (blobFound) { this.insertFile(blobFound) e.preventDefault() return } else { const item_list = await navigator.clipboard.read() + let image_type + const item = item_list.find(item => item.types.some(type => { if (type.startsWith('image/')) { @@ -1048,10 +1293,11 @@ class ChatPage extends LitElement { } }) ) + if (item) { try { const blob = item && await item.getType(image_type) - let file = new File([blob], "name", { + let file = new File([blob], 'name', { type: image_type }) this.insertFile(file) @@ -1196,17 +1442,47 @@ class ChatPage extends LitElement { } insertFile(file) { + const acceptedFileExtension = [ + 'zip', 'jar', 'gzip', 'exe', 'deb', + 'rar', 'dmg', 'pkg', '7z', 'gz', 'psd', + 'mp4', 'rpm', 'snap', 'AppImage' + ] + + const acceptedAttachmentExtension = [ + 'pdf', 'txt', 'odt', 'ods', 'doc', 'html', + 'docx', 'xls', 'xlsx', 'ppt', 'pptx', 'sh', 'log' + ] + + const fileExtension = file.name.split('.').pop() + if (file.identifier) { this.imageFile = file this.currentEditor = 'newChat' + this.editorImage.commands.setContent('') + return + } else if (file.type === 'image/gif') { + this.gifFile = file + this.currentEditor = 'newGifChat' + this.editorGif.commands.setContent('') return } else if (file.type.includes('image')) { this.imageFile = file this.currentEditor = 'newChat' + this.editorImage.commands.setContent('') return - } else { + } else if (acceptedFileExtension.includes(fileExtension)) { + this.appFile = file + this.currentEditor = 'newFileChat' + this.editorFile.commands.setContent('') + return + } else if (acceptedAttachmentExtension.includes(fileExtension)){ this.attachment = file this.currentEditor = "newAttachmentChat" + this.editorAttachment.commands.setContent('') + return + } else { + this.resetChatEditor() + parentEpml.request('showSnackBar', get("chatpage.cchange109")) return } } @@ -1217,12 +1493,24 @@ class ChatPage extends LitElement { this.currentEditor = '_chatEditorDOM' } + removeGif() { + this.gifFile = null + this.resetChatEditor() + this.currentEditor = '_chatEditorDOM' + } + removeAttachment() { this.attachment = null this.resetChatEditor() this.currentEditor = '_chatEditorDOM' } + removeAppFile() { + this.appFile = null + this.resetChatEditor() + this.currentEditor = '_chatEditorDOM' + } + changeMsgInput(id) { this.chatMessageInput = this.shadowRoot.getElementById(id) this.initChatEditor() @@ -1233,7 +1521,9 @@ class ChatPage extends LitElement { this.webSocket.close(1000, 'switch chat') this.webSocket = '' } + this.pageNumber = 1 + const getAddressPublicKey = () => { parentEpml.request('apiCall', { type: 'api', @@ -1260,18 +1550,16 @@ class ChatPage extends LitElement { this.chatId.includes('direct') === true ? this.isReceipient = true : this.isReceipient = false this._chatId = this.chatId.split('/')[1] - const mstring = get("chatpage.cchange8") + const mstring = get('chatpage.cchange114') const placeholder = isRecipient === true ? `Message ${this._chatId}` : `${mstring}` this.chatEditorPlaceholder = placeholder isRecipient ? getAddressPublicKey() : this.fetchChatMessages(this._chatId) - - // Init ChatEditor - // this.initChatEditor() }, 100) const isRecipient = this.chatId.includes('direct') === true ? true : false const groupId = this.chatId.split('/')[1] + if (!isRecipient && groupId.toString() !== '0') { try { const getMembers = await parentEpml.request("apiCall", { @@ -1314,7 +1602,9 @@ class ChatPage extends LitElement { } catch (error) { /* empty */ } return memberItem }) + const membersWithName = await Promise.all(getMembersWithName) + this.groupAdmin = membersAdminsWithName this.groupMembers = membersWithName this.groupInfo = getGroupInfo @@ -1337,7 +1627,7 @@ class ChatPage extends LitElement { const userLang = changedProperties.get('userLanguage') if (userLang) { await new Promise(r => setTimeout(r, 100)) - this.chatEditorPlaceholder = this.isReceipient === true ? `Message ${this._chatId}` : `${get("chatpage.cchange8")}` + this.chatEditorPlaceholder = this.isReceipient === true ? `Message ${this._chatId}` : `${get('chatpage.cchange114')}` } } @@ -1345,10 +1635,12 @@ class ChatPage extends LitElement { if (this.isLoading === true && this.currentEditor === '_chatEditorDOM' && this.editor && this.editor.setEditable) { this.editor.setEditable(false) } + if (this.isLoading === false && this.currentEditor === '_chatEditorDOM' && this.editor && this.editor.setEditable) { this.editor.setEditable(true) } } + if (changedProperties && changedProperties.has('chatId') && this.webSocket) { const previousChatId = changedProperties.get('chatId') @@ -1427,12 +1719,16 @@ class ChatPage extends LitElement { return "" } } - let userName = "" + + let userName = '' + if (this.isReceipient) { userName = await getName(this._chatId) } - const mstring = get("chatpage.cchange8") + + const mstring = get('chatpage.cchange114') const placeholder = this.isReceipient === true ? `Message ${userName ? userName : this._chatId}` : `${mstring}` + return placeholder } @@ -1865,13 +2161,14 @@ class ChatPage extends LitElement { const signature = item.originalSignature || item.signature newObj[signature] = item }) + this.updateMessageHash = { ...this.updateMessageHash, ...newObj } + this.requestUpdate() await this.getUpdateComplete() - } async clearUpdateMessageHashmap() { @@ -1895,31 +2192,35 @@ class ChatPage extends LitElement { } } } + return null } async processMessages(messages, isInitial, isUnread, count) { const isReceipient = this.chatId.includes('direct') let decodedMessages = [] + if (!this.webWorkerDecodeMessages) { this.webWorkerDecodeMessages = new WebWorkerDecodeMessages() } + if (!this.webWorkerSortMessages) { this.webWorkerSortMessages = new WebWorkerSortMessages() } + await new Promise((res, rej) => { this.webWorkerDecodeMessages.postMessage({ messages: messages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }) this.webWorkerDecodeMessages.onmessage = e => { decodedMessages = e.data res() - } + this.webWorkerDecodeMessages.onerror = () => { rej() - } }) + if (isInitial) { this.chatEditorPlaceholder = await this.renderPlaceholder() @@ -1953,7 +2254,6 @@ class ChatPage extends LitElement { const lastReadMessageTimestamp = this.lastReadMessageTimestamp if (isUnread) { - this.messagesRendered = { messages: this._messages, type: 'initialLastSeen', @@ -2045,6 +2345,7 @@ class ChatPage extends LitElement { } let viewElement = this.shadowRoot.querySelector('chat-scroller') + if (viewElement) { viewElement = viewElement.shadowRoot.getElementById('viewElement') } else { @@ -2052,34 +2353,34 @@ class ChatPage extends LitElement { } if (newMessage.sender === this.selectedAddress.address) { - - this.messagesRendered = { messages: [newMessage], type: 'newComingInAuto', } + await this.getUpdateComplete() // viewElement.scrollTop = viewElement.scrollHeight } else if (this.isUserDown) { - this.messagesRendered = { messages: [newMessage], type: 'newComingInAuto', } + // Append the message and scroll to the bottom if user is down the page // this.messagesRendered = [...this.messagesRendered, newMessage] await this.getUpdateComplete() + if (viewElement) { viewElement.scrollTop = viewElement.scrollHeight } } else { - this.messagesRendered = { messages: [newMessage], type: 'newComingInAuto', } + await this.getUpdateComplete() this.showNewMessageBar() @@ -2094,6 +2395,7 @@ class ChatPage extends LitElement { decodeMessage(encodedMessageObj, isReceipient, _publicKey) { let isReceipientVar let _publicKeyVar + try { isReceipientVar = this.isReceipient === undefined ? isReceipient : this.isReceipient _publicKeyVar = this._publicKey === undefined ? _publicKey : this._publicKey @@ -2115,12 +2417,12 @@ class ChatPage extends LitElement { } else { decodedMessageObj = { ...encodedMessageObj, decodedMessage: "Cannot Decrypt Message!" } } - } else { // group chat let decodedMessage = window.parent.Base64.decode(encodedMessageObj.data) decodedMessageObj = { ...encodedMessageObj, decodedMessage } } + return decodedMessageObj } @@ -2144,6 +2446,7 @@ class ChatPage extends LitElement { } this.webSocket = new WebSocket(directSocketLink) + // Open Connection this.webSocket.onopen = () => { setTimeout(pingDirectSocket, 50) @@ -2375,9 +2678,19 @@ class ChatPage extends LitElement { if (this.currentEditor === '_chatEditorDOM') { this.editor.commands.setContent('') } + if (this.currentEditor === 'newChat') { this.editorImage.commands.setContent('') } + + if (this.currentEditor === 'newGifChat') { + this.editorGif.commands.setContent('') + } + + if (this.currentEditor === 'newFileChat') { + this.editorAttachment.commands.setContent('') + } + if (this.currentEditor === 'newAttachmentChat') { this.editorAttachment.commands.setContent('') } @@ -2386,8 +2699,9 @@ class ChatPage extends LitElement { async _sendMessage(outSideMsg, msg, messageQueue) { const _chatId = this._chatId const isReceipient = this.isReceipient + const str = "iVBORw0KGgoAAAANSUhEUgAAAsAAAAGMAQMAAADuk4YmAAAAA1BMVEX///+nxBvIAAAAAXRSTlMAQObYZgAAADlJREFUeF7twDEBAAAAwiD7p7bGDlgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAGJrAABgPqdWQAAAABJRU5ErkJggg==" + let _publicKey = this._publicKey - const attachment = this.attachment try { if (this.isReceipient) { @@ -2420,14 +2734,16 @@ class ChatPage extends LitElement { } } + // have params to determine if it's a reply or not // have variable to determine if it's a response, holds signature in constructor // need original message signature // need whole original message object, transform the data and put it in local storage // create new var called repliedToData and use that to modify the UI // find specific object property in local + let typeMessage = 'regular' - // this.isLoading = true + const trimmedMessage = msg const getName = async (recipient) => { @@ -2444,16 +2760,18 @@ class ChatPage extends LitElement { } } catch (error) { - return "" + return '' } } if (outSideMsg && outSideMsg.type === 'delete') { this.isDeletingImage = true - const userName = outSideMsg.name - const identifier = outSideMsg.identifier - let compressedFile = '' - var str = "iVBORw0KGgoAAAANSUhEUgAAAsAAAAGMAQMAAADuk4YmAAAAA1BMVEX///+nxBvIAAAAAXRSTlMAQObYZgAAADlJREFUeF7twDEBAAAAwiD7p7bGDlgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAGJrAABgPqdWQAAAABJRU5ErkJggg==" + + let userName + let identifier + + userName = outSideMsg.name + identifier = outSideMsg.identifier if (this.webWorkerFile) { this.webWorkerFile.terminate() @@ -2481,7 +2799,11 @@ class ChatPage extends LitElement { const blob = new Blob(byteArrays, { type: contentType }) return blob } + const blob = b64toBlob(str, 'image/png') + + let compressedFile = '' + await new Promise(resolve => { new Compressor(blob, { quality: 0.6, @@ -2498,13 +2820,15 @@ class ChatPage extends LitElement { }, }) }) + const arbitraryFeeData = await modalHelper.getArbitraryFee() - const res = await modalHelper.showModalAndWaitPublish( - { - feeAmount: arbitraryFeeData.feeToShow - } - ) + + const res = await modalHelper.showModalAndWaitPublish({ + feeAmount: arbitraryFeeData.feeToShow + }) + if (res.action !== 'accept') throw new Error('User declined publish') + try { await publishData({ registeredName: userName, @@ -2519,37 +2843,158 @@ class ChatPage extends LitElement { withFee: true, feeAmount: arbitraryFeeData.fee }) + this.isDeletingImage = false } catch (error) { this.isLoading = false return } + typeMessage = 'edit' + let chatReference = outSideMsg.editedMessageObj.signature if (outSideMsg.editedMessageObj.chatReference) { chatReference = outSideMsg.editedMessageObj.chatReference } - let message = "" + let message = '' + try { const parsedMessageObj = JSON.parse(outSideMsg.editedMessageObj.decodedMessage) message = parsedMessageObj } catch (error) { message = outSideMsg.editedMessageObj.decodedMessage } + const messageObject = { ...message, isImageDeleted: true } + + const stringifyMessageObject = JSON.stringify(messageObject) + + return this.sendMessage({ messageText: stringifyMessageObject, typeMessage, chatReference, isForward: false, isReceipient, _chatId, _publicKey, messageQueue }) + } else if (outSideMsg && outSideMsg.type === 'deleteGif') { + this.isDeletingGif = true + + let userName + let identifier + + userName = outSideMsg.name + identifier = outSideMsg.identifier + + if (this.webWorkerFile) { + this.webWorkerFile.terminate() + this.webWorkerFile = null + } + + this.webWorkerFile = new WebWorkerFile() + + const b64toBlob = (b64Data, contentType = '', sliceSize = 512) => { + const byteCharacters = atob(b64Data) + const byteArrays = [] + + for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) { + const slice = byteCharacters.slice(offset, offset + sliceSize) + + const byteNumbers = new Array(slice.length) + for (let i = 0; i < slice.length; i++) { + byteNumbers[i] = slice.charCodeAt(i) + } + + const byteArray = new Uint8Array(byteNumbers) + byteArrays.push(byteArray) + } + + const blob = new Blob(byteArrays, { type: contentType }) + return blob + } + + const blob = b64toBlob(str, 'image/png') + + let compressedFile = '' + + await new Promise(resolve => { + new Compressor(blob, { + quality: 0.6, + maxWidth: 500, + success(result) { + const file = new File([result], "name", { + type: 'image/png' + }) + + compressedFile = file + resolve() + }, + error() { + }, + }) + }) + + const arbitraryFeeData = await modalHelper.getArbitraryFee() + + const res = await modalHelper.showModalAndWaitPublish({ + feeAmount: arbitraryFeeData.feeToShow + }) + + if (res.action !== 'accept') throw new Error('User declined publish') + + try { + await publishData({ + registeredName: userName, + file: compressedFile, + service: 'IMAGE', + identifier: identifier, + parentEpml, + metaData: undefined, + uploadType: 'file', + selectedAddress: this.selectedAddress, + worker: this.webWorkerFile, + withFee: true, + feeAmount: arbitraryFeeData.fee + }) + + this.isDeletingGif = false + } catch (error) { + this.isLoading = false + return + } + + typeMessage = 'edit' + + let chatReference = outSideMsg.editedMessageObj.signature + + if (outSideMsg.editedMessageObj.chatReference) { + chatReference = outSideMsg.editedMessageObj.chatReference + } + + let message = '' + + try { + const parsedMessageObj = JSON.parse(outSideMsg.editedMessageObj.decodedMessage) + message = parsedMessageObj + + } catch (error) { + message = outSideMsg.editedMessageObj.decodedMessage + } + + const messageObject = { + ...message, + isGifDeleted: true + } + const stringifyMessageObject = JSON.stringify(messageObject) + return this.sendMessage({ messageText: stringifyMessageObject, typeMessage, chatReference, isForward: false, isReceipient, _chatId, _publicKey, messageQueue }) } else if (outSideMsg && outSideMsg.type === 'deleteAttachment') { this.isDeletingAttachment = true - let compressedFile = '' - const str = "iVBORw0KGgoAAAANSUhEUgAAAsAAAAGMAQMAAADuk4YmAAAAA1BMVEX///+nxBvIAAAAAXRSTlMAQObYZgAAADlJREFUeF7twDEBAAAAwiD7p7bGDlgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAGJrAABgPqdWQAAAABJRU5ErkJggg==" - const userName = outSideMsg.name - const identifier = outSideMsg.identifier + + let userName + let identifier + + userName = outSideMsg.name + identifier = outSideMsg.identifier if (this.webWorkerFile) { this.webWorkerFile.terminate() @@ -2579,6 +3024,9 @@ class ChatPage extends LitElement { } const blob = b64toBlob(str, 'image/png') + + let compressedFile = '' + await new Promise(resolve => { new Compressor(blob, { quality: 0.6, @@ -2595,18 +3043,20 @@ class ChatPage extends LitElement { }, }) }) + const arbitraryFeeData = await modalHelper.getArbitraryFee() - const res = await modalHelper.showModalAndWaitPublish( - { - feeAmount: arbitraryFeeData.feeToShow - } - ) + + const res = await modalHelper.showModalAndWaitPublish({ + feeAmount: arbitraryFeeData.feeToShow + }) + if (res.action !== 'accept') throw new Error('User declined publish') + try { await publishData({ registeredName: userName, file: compressedFile, - service: 'QCHAT_ATTACHMENT', + service: 'ATTACHMENT', identifier: identifier, parentEpml, metaData: undefined, @@ -2616,19 +3066,23 @@ class ChatPage extends LitElement { withFee: true, feeAmount: arbitraryFeeData.fee }) + this.isDeletingAttachment = false } catch (error) { this.isLoading = false return } + typeMessage = 'edit' + let chatReference = outSideMsg.editedMessageObj.signature if (outSideMsg.editedMessageObj.chatReference) { chatReference = outSideMsg.editedMessageObj.chatReference } - let message = "" + let message = '' + try { const parsedMessageObj = JSON.parse(outSideMsg.editedMessageObj.decodedMessage) message = parsedMessageObj @@ -2636,17 +3090,135 @@ class ChatPage extends LitElement { } catch (error) { message = outSideMsg.editedMessageObj.decodedMessage } + const messageObject = { ...message, isAttachmentDeleted: true } + const stringifyMessageObject = JSON.stringify(messageObject) + + return this.sendMessage({ messageText: stringifyMessageObject, typeMessage, chatReference, isForward: false, isReceipient, _chatId, _publicKey, messageQueue }) + } else if (outSideMsg && outSideMsg.type === 'deleteFile') { + this.isDeletingAppFile = true + + let userName + let identifier + + userName = outSideMsg.name + identifier = outSideMsg.identifier + + if (this.webWorkerFile) { + this.webWorkerFile.terminate() + this.webWorkerFile = null + } + + this.webWorkerFile = new WebWorkerFile() + + const b64toBlob = (b64Data, contentType = '', sliceSize = 512) => { + const byteCharacters = atob(b64Data) + const byteArrays = [] + + for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) { + const slice = byteCharacters.slice(offset, offset + sliceSize) + + const byteNumbers = new Array(slice.length) + for (let i = 0; i < slice.length; i++) { + byteNumbers[i] = slice.charCodeAt(i) + } + + const byteArray = new Uint8Array(byteNumbers) + byteArrays.push(byteArray) + } + + const blob = new Blob(byteArrays, { type: contentType }) + return blob + } + + const blob = b64toBlob(str, 'image/png') + + let compressedFile = '' + + await new Promise(resolve => { + new Compressor(blob, { + quality: 0.6, + maxWidth: 500, + success(result) { + const file = new File([result], "name", { + type: 'image/png' + }) + + compressedFile = file + resolve() + }, + error() { + }, + }) + }) + + const arbitraryFeeData = await modalHelper.getArbitraryFee() + + const res = await modalHelper.showModalAndWaitPublish({ + feeAmount: arbitraryFeeData.feeToShow + }) + + if (res.action !== 'accept') throw new Error('User declined publish') + + try { + await publishData({ + registeredName: userName, + file: compressedFile, + service: 'FILE', + identifier: identifier, + parentEpml, + metaData: undefined, + uploadType: 'file', + selectedAddress: this.selectedAddress, + worker: this.webWorkerFile, + withFee: true, + feeAmount: arbitraryFeeData.fee + }) + + this.isDeletingAppFile = false + } catch (error) { + this.isDeletingAppFile = false + this.isLoading = false + return + } + + typeMessage = 'edit' + + let chatReference = outSideMsg.editedMessageObj.signature + + if (outSideMsg.editedMessageObj.chatReference) { + chatReference = outSideMsg.editedMessageObj.chatReference + } + + let message = '' + + try { + const parsedMessageObj = JSON.parse(outSideMsg.editedMessageObj.decodedMessage) + message = parsedMessageObj + + } catch (error) { + message = outSideMsg.editedMessageObj.decodedMessage + } + + const messageObject = { + ...message, + isFileDeleted: true + } + + const stringifyMessageObject = JSON.stringify(messageObject) + return this.sendMessage({ messageText: stringifyMessageObject, typeMessage, chatReference, isForward: false, isReceipient, _chatId, _publicKey, messageQueue }) } else if (outSideMsg && outSideMsg.type === 'image') { if (!this.imageFile.identifier) { this.isUploadingImage = true } + const userName = await getName(this.selectedAddress.address) + if (!userName) { parentEpml.request('showSnackBar', get("chatpage.cchange27")) this.isLoading = false @@ -2655,21 +3227,21 @@ class ChatPage extends LitElement { return } - let service = "QCHAT_IMAGE" let name = userName let identifier + if (this.imageFile.identifier) { identifier = this.imageFile.identifier name = this.imageFile.name service = this.imageFile.service } else { const arbitraryFeeData = await modalHelper.getArbitraryFee() - const res = await modalHelper.showModalAndWaitPublish( - { - feeAmount: arbitraryFeeData.feeToShow - } - ) + + const res = await modalHelper.showModalAndWaitPublish({ + feeAmount: arbitraryFeeData.feeToShow + }) + if (res.action !== 'accept') throw new Error('User declined publish') if (this.webWorkerFile) { @@ -2678,16 +3250,21 @@ class ChatPage extends LitElement { } this.webWorkerFile = new WebWorkerFile() + const image = this.imageFile const id = this.uid.rnd() let groupPart + if (this.isReceipient) { groupPart = `direct_${generateIdFromAddresses(this._chatId, this.selectedAddress.address)}` } else { groupPart = `group_${this._chatId}` } + identifier = `qchat_${groupPart}_${id}` + let compressedFile = '' + await new Promise(resolve => { new Compressor(image, { quality: .6, @@ -2704,7 +3281,9 @@ class ChatPage extends LitElement { }, }) }) + const fileSize = compressedFile.size + if (fileSize > 500000) { parentEpml.request('showSnackBar', get("chatpage.cchange26")) this.isLoading = false @@ -2713,7 +3292,6 @@ class ChatPage extends LitElement { } try { - await publishData({ registeredName: userName, file: compressedFile, @@ -2727,6 +3305,7 @@ class ChatPage extends LitElement { withFee: true, feeAmount: arbitraryFeeData.fee }) + this.isUploadingImage = false this.removeImage() } catch (error) { @@ -2742,42 +3321,124 @@ class ChatPage extends LitElement { images: [{ service: service, name: name, - identifier: identifier, + identifier: identifier }], isImageDeleted: false, repliedTo: '', version: 3 } + const stringifyMessageObject = JSON.stringify(messageObject) + this.removeImage() + return this.sendMessage({ messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue }) } else if (outSideMsg && outSideMsg.type === 'gif') { + if (!this.gifFile.identifier) { + this.isUploadingGif = true + } + const userName = await getName(this.selectedAddress.address) + if (!userName) { parentEpml.request('showSnackBar', get("chatpage.cchange27")) this.isLoading = false + this.isUploadingGif = false + this.gifFile = null + return + } + + let identifier + let groupPart + + if (this.webWorkerFile) { + this.webWorkerFile.terminate() + this.webWorkerFile = null + } + + this.webWorkerFile = new WebWorkerFile() + + const gifFile = this.gifFile + const id = this.uid.rnd() + + if (this.isReceipient) { + groupPart = `direct_${generateIdFromAddresses(this._chatId, this.selectedAddress.address)}` + } else { + groupPart = `group_${this._chatId}` + } + + identifier = `qchat_${groupPart}_gif_${id}` + + const fileSize = gifFile.size + + if (fileSize > 3000000) { + parentEpml.request('showSnackBar', get("chatpage.cchange103")) + this.isLoading = false + this.isUploadingGif = false + return + } + + const arbitraryFeeData = await modalHelper.getArbitraryFee() + + const res = await modalHelper.showModalAndWaitPublish({ + feeAmount: arbitraryFeeData.feeToShow + }) + + if (res.action !== 'accept') throw new Error('User declined publish') + + try { + await publishData({ + registeredName: userName, + file: gifFile, + service: 'IMAGE', + identifier: identifier, + parentEpml, + metaData: undefined, + uploadType: 'file', + selectedAddress: this.selectedAddress, + worker: this.webWorkerFile, + withFee: true, + feeAmount: arbitraryFeeData.fee + }) + + this.isUploadingGif = false + this.removeGif() + } catch (error) { + this.isLoading = false + this.isUploadingGif = false return } const messageObject = { - messageText: '', + messageText: trimmedMessage, gifs: [{ - service: outSideMsg.service, - name: outSideMsg.name, - identifier: outSideMsg.identifier, - filePath: outSideMsg.filePath + service: 'IMAGE', + name: userName, + identifier: identifier }], + isGifDeleted: false, repliedTo: '', version: 3 } + const stringifyMessageObject = JSON.stringify(messageObject) + return this.sendMessage({ messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue }) } else if (outSideMsg && outSideMsg.type === 'attachment') { - this.isUploadingAttachment = true + if (!this.attachment.identifier) { + this.isUploadingAttachment = true + } + + let identifier + let groupPart + const userName = await getName(this.selectedAddress.address) + if (!userName) { parentEpml.request('showSnackBar', get("chatpage.cchange27")) + this.isUploadingAttachment = false this.isLoading = false + this.attachment = null return } @@ -2788,28 +3449,39 @@ class ChatPage extends LitElement { this.webWorkerFile = new WebWorkerFile() - // const attachment = attachment - const id = this.uid() - const identifier = `qchat_${id}` + const attachment = this.attachment + const id = this.uid.rnd() + + if (this.isReceipient) { + groupPart = `direct_${generateIdFromAddresses(this._chatId, this.selectedAddress.address)}` + } else { + groupPart = `group_${this._chatId}` + } + + identifier = `qchat_${groupPart}_attachment_${id}` + const fileSize = attachment.size - if (fileSize > 1000000) { + + if (fileSize > 10000000) { parentEpml.request('showSnackBar', get("chatpage.cchange77")) this.isLoading = false this.isUploadingAttachment = false return } + const arbitraryFeeData = await modalHelper.getArbitraryFee() - const res = await modalHelper.showModalAndWaitPublish( - { - feeAmount: arbitraryFeeData.feeToShow - } - ) + + const res = await modalHelper.showModalAndWaitPublish({ + feeAmount: arbitraryFeeData.feeToShow + }) + if (res.action !== 'accept') throw new Error('User declined publish') + try { await publishData({ registeredName: userName, file: attachment, - service: 'QCHAT_ATTACHMENT', + service: 'ATTACHMENT', identifier: identifier, parentEpml, metaData: undefined, @@ -2819,6 +3491,7 @@ class ChatPage extends LitElement { withFee: true, feeAmount: arbitraryFeeData.fee }) + this.isUploadingAttachment = false this.removeAttachment() } catch (error) { @@ -2826,10 +3499,11 @@ class ChatPage extends LitElement { this.isUploadingAttachment = false return } + const messageObject = { messageText: trimmedMessage, attachments: [{ - service: 'QCHAT_ATTACHMENT', + service: 'ATTACHMENT', name: userName, identifier: identifier, attachmentName: attachment.name, @@ -2839,18 +3513,115 @@ class ChatPage extends LitElement { repliedTo: '', version: 3 } + + const stringifyMessageObject = JSON.stringify(messageObject) + + return this.sendMessage({ messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue }) + } else if (outSideMsg && outSideMsg.type === 'file') { + if (!this.appFile.identifier) { + this.isUploadingAppFile = true + } + + let identifier + let groupPart + + const userName = await getName(this.selectedAddress.address) + + if (!userName) { + parentEpml.request('showSnackBar', get("chatpage.cchange27")) + this.isUploadingAppFile = false + this.isLoading = false + this.appFile = null + return + } + + if (this.webWorkerFile) { + this.webWorkerFile.terminate() + this.webWorkerFile = null + } + + this.webWorkerFile = new WebWorkerFile() + + const appFile = this.appFile + const id = this.uid.rnd() + + if (this.isReceipient) { + groupPart = `direct_${generateIdFromAddresses(this._chatId, this.selectedAddress.address)}` + } else { + groupPart = `group_${this._chatId}` + } + + identifier = `qchat_${groupPart}_file_${id}` + + const fileSize = appFile.size + + if (fileSize > 125000000) { + parentEpml.request('showSnackBar', get("chatpage.cchange100")) + this.isLoading = false + this.isUploadingAppFile = false + return + } + + const arbitraryFeeData = await modalHelper.getArbitraryFee() + + const res = await modalHelper.showModalAndWaitPublish({ + feeAmount: arbitraryFeeData.feeToShow + }) + + if (res.action !== 'accept') throw new Error('User declined publish') + + try { + await publishData({ + registeredName: userName, + file: appFile, + service: 'FILE', + identifier: identifier, + parentEpml, + metaData: undefined, + uploadType: 'file', + selectedAddress: this.selectedAddress, + worker: this.webWorkerFile, + withFee: true, + feeAmount: arbitraryFeeData.fee + }) + + this.isUploadingAppFile = false + this.removeAppFile() + } catch (error) { + this.isLoading = false + this.isUploadingAppFile = false + return + } + + const messageObject = { + messageText: trimmedMessage, + files: [{ + service: 'FILE', + name: userName, + identifier: identifier, + appFileName: appFile.name, + appFileSize: appFile.size + }], + isFileDeleted: false, + repliedTo: '', + version: 3 + } + const stringifyMessageObject = JSON.stringify(messageObject) + return this.sendMessage({ messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue }) } else if (outSideMsg && outSideMsg.type === 'reaction') { const userName = await getName(this.selectedAddress.address) + typeMessage = 'edit' + let chatReference = outSideMsg.editedMessageObj.signature if (outSideMsg.editedMessageObj.chatReference) { chatReference = outSideMsg.editedMessageObj.chatReference } - let message = "" + let message = '' try { const parsedMessageObj = JSON.parse(outSideMsg.editedMessageObj.decodedMessage) @@ -2860,10 +3631,13 @@ class ChatPage extends LitElement { } let reactions = message.reactions || [] + const findEmojiIndex = reactions.findIndex((reaction) => reaction.type === outSideMsg.reaction) + if (findEmojiIndex !== -1) { let users = reactions[findEmojiIndex].users || [] const findUserIndex = users.findIndex((user) => user.address === this.selectedAddress.address) + if (findUserIndex !== -1) { users.splice(findUserIndex, 1) } else { @@ -2872,11 +3646,13 @@ class ChatPage extends LitElement { name: userName }) } + reactions[findEmojiIndex] = { ...reactions[findEmojiIndex], qty: users.length, users } + if (users.length === 0) { reactions.splice(findEmojiIndex, 1) } @@ -2890,27 +3666,35 @@ class ChatPage extends LitElement { }] }] } + const messageObject = { ...message, reactions } + const stringifyMessageObject = JSON.stringify(messageObject) + return this.sendMessage({ messageText: stringifyMessageObject, typeMessage, chatReference, isForward: false, isReceipient, _chatId, _publicKey, messageQueue }) } else if (/^\s*$/.test(trimmedMessage)) { this.isLoading = false } else if (this.repliedToMessageObj) { let chatReference = this.repliedToMessageObj.signature + if (this.repliedToMessageObj.chatReference) { chatReference = this.repliedToMessageObj.chatReference } + typeMessage = 'reply' + const messageObject = { messageText: trimmedMessage, images: [''], repliedTo: chatReference, version: 3 } + const stringifyMessageObject = JSON.stringify(messageObject) + return this.sendMessage({ messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue }) } else if (this.editedMessageObj) { typeMessage = 'edit' @@ -2920,7 +3704,8 @@ class ChatPage extends LitElement { chatReference = this.editedMessageObj.chatReference } - let message = "" + let message = '' + try { const parsedMessageObj = JSON.parse(this.editedMessageObj.decodedMessage) message = parsedMessageObj @@ -2928,12 +3713,15 @@ class ChatPage extends LitElement { } catch (error) { message = this.editedMessageObj.decodedMessage } + const messageObject = { ...message, messageText: trimmedMessage, isEdited: true } + const stringifyMessageObject = JSON.stringify(messageObject) + return this.sendMessage({ messageText: stringifyMessageObject, typeMessage, chatReference, isForward: false, isReceipient, _chatId, _publicKey, messageQueue }) } else { const messageObject = { @@ -2942,6 +3730,7 @@ class ChatPage extends LitElement { repliedTo: '', version: 3 } + const stringifyMessageObject = JSON.stringify(messageObject) if (this.balance < 4) { @@ -2955,6 +3744,9 @@ class ChatPage extends LitElement { } catch (error) { this.isLoading = false this.isUploadingImage = false + this.isUploadingGif = false + this.isUploadingAttachment = false + this.isUploadingAppFile = false return } @@ -2968,6 +3760,7 @@ class ChatPage extends LitElement { this.closeRepliedToContainer() return } + if (isForward) { this.isLoading = true } @@ -2975,6 +3768,7 @@ class ChatPage extends LitElement { let _reference = new Uint8Array(64) window.crypto.getRandomValues(_reference) let reference = window.parent.Base58.encode(_reference) + const sendMessageRequest = async () => { if (isReceipient === true) { let chatResponse = await parentEpml.request('chat', { diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index 14963901..8086fe8a 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -21,6 +21,9 @@ import './ChatImage' import '@material/mwc-button' import '@material/mwc-dialog' import '@material/mwc-icon' +import '@polymer/paper-dialog/paper-dialog.js' +import '@polymer/paper-icon-button/paper-icon-button.js' +import '@polymer/iron-icons/iron-icons.js' import '@vaadin/icon' import '@vaadin/icons' import '@vaadin/tooltip' @@ -96,6 +99,7 @@ function processText(input) { parts.forEach((part) => { if (part.startsWith('qortal://')) { const link = document.createElement('span') + // Store the URL in a data attribute link.setAttribute('data-url', part) link.textContent = part @@ -107,7 +111,9 @@ function processText(input) { e.preventDefault() try { const res = await extractComponents(part) + if (!res) return + if (res.type && res.groupid && res.action === 'join') { window.parent.reduxStore.dispatch( window.parent.reduxAction.setNewTab({ @@ -130,7 +136,7 @@ function processText(input) { window.parent.reduxStore.dispatch( window.parent.reduxAction.setSideEffectAction({ type: 'openJoinGroupModal', - data: +res.groupid + data: res.groupid }) ) return @@ -987,7 +993,9 @@ class MessageTemplate extends LitElement { openDialogImage: { type: Boolean }, openDialogGif: { type: Boolean }, openDeleteImage: { type: Boolean }, + openDeleteGif: { type: Boolean }, openDeleteAttachment: { type: Boolean }, + openDeleteFile: { type: Boolean }, isImageLoaded: { type: Boolean }, isGifLoaded: { type: Boolean }, isFirstMessage: { type: Boolean }, @@ -1032,6 +1040,10 @@ class MessageTemplate extends LitElement { this.isLastMessageInGroup = false this.viewImage = false this.isInProgress = false + this.openDeleteImage = false + this.openDeleteGif = false + this.openDeleteAttachment = false + this.openDeleteFile = false } render() { @@ -1044,36 +1056,50 @@ class MessageTemplate extends LitElement { let repliedToData = null let image = null let gif = null + let attachment = null + let file = null let isImageDeleted = false + let isGifDeleted = false let isAttachmentDeleted = false + let isFileDeleted = false let version = 0 let isForwarded = false let isEdited = false - let attachment = null try { const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage) + if (+parsedMessageObj.version > 1 && parsedMessageObj.messageText) { messageVersion2 = generateHTML(parsedMessageObj.messageText, [StarterKit, Underline, Highlight]) messageVersion2WithLink = processText(messageVersion2) } + message = parsedMessageObj.messageText repliedToData = this.messageObj.repliedToData isImageDeleted = parsedMessageObj.isImageDeleted + isGifDeleted = parsedMessageObj.isGifDeleted isAttachmentDeleted = parsedMessageObj.isAttachmentDeleted + isFileDeleted = parsedMessageObj.isFileDeleted // reactions = parsedMessageObj.reactions || [] version = parsedMessageObj.version isForwarded = parsedMessageObj.type === 'forward' isEdited = parsedMessageObj.isEdited && true - if (parsedMessageObj.attachments && Array.isArray(parsedMessageObj.attachments) && parsedMessageObj.attachments.length > 0) { - attachment = parsedMessageObj.attachments[0] - } + if (parsedMessageObj.images && Array.isArray(parsedMessageObj.images) && parsedMessageObj.images.length > 0) { image = parsedMessageObj.images[0] } + if (parsedMessageObj.gifs && Array.isArray(parsedMessageObj.gifs) && parsedMessageObj.gifs.length > 0) { gif = parsedMessageObj.gifs[0] } + + if (parsedMessageObj.attachments && Array.isArray(parsedMessageObj.attachments) && parsedMessageObj.attachments.length > 0) { + attachment = parsedMessageObj.attachments[0] + } + + if (parsedMessageObj.files && Array.isArray(parsedMessageObj.files) && parsedMessageObj.files.length > 0) { + file = parsedMessageObj.files[0] + } } catch (error) { message = this.messageObj.decodedMessage } @@ -1141,7 +1167,7 @@ class MessageTemplate extends LitElement { if (gif) { const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port - gifUrl = `${nodeUrl}/arbitrary/${gif.service}/${gif.name}/${gif.identifier}?filepath=${gif.filePath}` + gifUrl = `${nodeUrl}/arbitrary/${gif.service}/${gif.name}/${gif.identifier}?async=true` if (this.viewImage || this.myAddress === this.messageObj.sender) { gifHTML = createGif(gifUrl) gifHTMLDialog = createGif(gifUrl) @@ -1280,7 +1306,7 @@ class MessageTemplate extends LitElement { class=${[`image-container`, !this.isImageLoaded ? 'defaultSize' : '',].join(' ')} style=${this.isFirstMessage && 'margin-top: 10px;'} > -
+
${translate('chatpage.cchange40')}
@@ -1293,7 +1319,12 @@ class MessageTemplate extends LitElement { ${imageHTML} ${this.myAddress === this.messageObj.sender ? html` - {this.openDeleteImage = true;}} class="image-delete-icon" icon="vaadin:close" slot="icon"> + this.openDeleteImageDialog()} + icon="vaadin:close" + slot="icon" + class="image-delete-icon" + > ` : '' } @@ -1301,35 +1332,62 @@ class MessageTemplate extends LitElement { ` : image && isImageDeleted ? html` -

${translate('chatpage.cchange80')}

+
+
+

+ ${translate('chatpage.cchange80')} +

+
+
` : html`` } - ${gif && !this.viewImage && this.myAddress !== this.messageObj.sender ? + ${gif && !isGifDeleted && !this.viewImage && this.myAddress !== this.messageObj.sender ? html`
{this.viewImage = true;}} - class=${[`image-container`, !this.isImageLoaded ? 'defaultSize' : '', ].join(' ')} + class=${[`image-container`, !this.isGifoaded ? 'defaultSize' : '', ].join(' ')} style=${this.isFirstMessage && 'margin-top: 10px;'} > -
+
${translate('gifs.gchange25')}
` : html`` } - ${gif && (this.viewImage || this.myAddress === this.messageObj.sender) ? + ${gif && !isGifDeleted && (this.viewImage || this.myAddress === this.messageObj.sender) ? html`
${gifHTML} + ${this.myAddress === this.messageObj.sender ? + html` + this.openDeleteGifDialog()} + icon="vaadin:close" + slot="icon" + class="image-delete-icon" + > + ` + : '' + }
` + : gif && isGifDeleted ? + html` +
+
+

+ ${translate('chatpage.cchange107')} +

+
+
+ ` : html`` } ${attachment && !isAttachmentDeleted ? html` -
await this.downloadAttachment(attachment)} class="attachment-container"> +
attachment-icon
@@ -1341,11 +1399,16 @@ class MessageTemplate extends LitElement { ${roundToNearestDecimal(attachment.attachmentSize)} mb

- + await this.downloadAttachment(attachment)} + icon="vaadin:download-alt" + slot="icon" + class="download-icon" + > ${this.myAddress === this.messageObj.sender ? html` {e.stopPropagation(); this.openDeleteAttachment = true;}} + @click=${() => this.openDeleteAttachmentDialog()} class="image-delete-icon" icon="vaadin:close" slot="icon" @@ -1368,6 +1431,51 @@ class MessageTemplate extends LitElement { ` : html`` } + ${file && !isFileDeleted ? + html` +
+
+ file-icon +
+
+

+ ${file && file.appFileName} +

+

+ ${roundToNearestDecimal(file.appFileSize)} mb +

+
+ await this.downloadFile(file)} + icon="vaadin:download-alt" + slot="icon" + class="download-icon" + > + ${this.myAddress === this.messageObj.sender ? + html` + this.openDeleteFileDialog()} + class="image-delete-icon" + icon="vaadin:close" + slot="icon" + > + ` + : html`` + } +
+ ` + : file && isFileDeleted ? + html` +
+
+

+ ${translate('chatpage.cchange102')} +

+
+
+ ` + : html`` + }
${+version > 1 ? messageVersion2WithLink ? html` @@ -1511,7 +1619,6 @@ class MessageTemplate extends LitElement { {this.openDialogGif = false;}}> - MessageTemplate
${gifHTMLDialog} @@ -1519,34 +1626,88 @@ class MessageTemplate extends LitElement { {this.openDialogGif = false;}}> ${translate('general.close')} - MessageTemplate - {this.openDeleteImage = false;}}> +

${translate('chatpage.cchange78')}

-
- {this.openDeleteAttachment = false;}}> + +
+

${translate('chatpage.cchange106')}

+
+ +
+

${translate('chatpage.cchange79')}

- +
+ +
+

${translate('chatpage.cchange101')}

+
+
+ + +
+
+
+
+
+
+
+
+
+
+

${translate('appspage.schange41')}

+
+ + ${translate('chatpage.cchange108')} + ` } @@ -1574,6 +1735,14 @@ class MessageTemplate extends LitElement { }, 60000) } + async closeDownloadProgressDialog() { + const closeDelay = ms => new Promise(res => setTimeout(res, ms)) + this.shadowRoot.getElementById('downloadProgressDialog').close() + this.shadowRoot.getElementById('closeProgressDialog').open() + await closeDelay(3000) + this.shadowRoot.getElementById('closeProgressDialog').close() + } + // Open & Close Private Message Chat Modal showPrivateMessageModal() { this.openDialogPrivateMessage = true @@ -1600,17 +1769,82 @@ class MessageTemplate extends LitElement { } } - async downloadAttachment(attachment) { + openDeleteImageDialog() { + this.openDeleteImage = true + this.shadowRoot.querySelector('#deleteImageDialog').show() + } + + closeDeleteImageDialog() { + this.shadowRoot.querySelector('#deleteImageDialog').close() + this.openDeleteImage = false + } + + openDeleteGifDialog() { + this.openDeleteGif = true + this.shadowRoot.querySelector('#deleteGifDialog').show() + } + + closeDeleteGifDialog() { + this.shadowRoot.querySelector('#deleteGifDialog').close() + this.openDeleteGif = false + } + + openDeleteAttachmentDialog() { + this.openDeleteAttachment = true + this.shadowRoot.querySelector('#deleteAttachmentDialog').show() + } + + closeDeleteAttachmentDialog() { + this.shadowRoot.querySelector('#deleteAttachmentDialog').close() + this.openDeleteAttachment = false + } + + openDeleteFileDialog() { + this.openDeleteFile = true + this.shadowRoot.querySelector('#deleteFileDialog').show() + } + + closeDeleteFileDialog() { + this.shadowRoot.querySelector('#deleteFileDialog').close() + this.openDeleteFile = false + } + + downloadAttachment(attachment) { const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port + this.shadowRoot.getElementById('downloadProgressDialog').open() + try { axios.get( - `${nodeUrl}/arbitrary/QCHAT_ATTACHMENT/${attachment.name}/${attachment.identifier}`, + `${nodeUrl}/arbitrary/ATTACHMENT/${attachment.name}/${attachment.identifier}`, { responseType: 'blob' } ).then((response) => { + this.shadowRoot.getElementById('downloadProgressDialog').close() let filename = attachment.attachmentName let blob = new Blob([response.data], { type: 'application/octet-stream' }) + this.shadowRoot.getElementById('downloadProgressDialog').close() + this.saveFileToDisk(blob, filename) + }) + } catch (error) { + console.error(error) + } + } + + downloadFile(file) { + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port + + this.shadowRoot.getElementById('downloadProgressDialog').open() + + try { + axios.get( + `${nodeUrl}/arbitrary/FILE/${file.name}/${file.identifier}`, + { responseType: 'blob' } + ).then((response) => { + this.shadowRoot.getElementById('downloadProgressDialog').close() + let filename = file.appFileName + let blob = new Blob([response.data], { type: 'application/octet-stream' }) this.saveFileToDisk(blob, filename) }) } catch (error) { @@ -1630,7 +1864,8 @@ class MessageTemplate extends LitElement { await writable.write(contents) await writable.close() } - writeFile(fileHandle, blob).then(() => console.log('FILE SAVED')) + + await writeFile(fileHandle, blob).then(() => console.log('FILE SAVED')) } catch (error) { console.log(error) } diff --git a/plugins/plugins/core/components/ChatTextEditor.js b/plugins/plugins/core/components/ChatTextEditor.js index 3d6d07d4..805fdd99 100644 --- a/plugins/plugins/core/components/ChatTextEditor.js +++ b/plugins/plugins/core/components/ChatTextEditor.js @@ -135,14 +135,18 @@ class ChatTextEditor extends LitElement { this.insertFile(e.target.files[0]) const filePickerInput = this.shadowRoot.getElementById('file-picker') if (filePickerInput) { - filePickerInput.value = "" + filePickerInput.value = '' } }}" id="file-picker" class="file-picker-input" type="file" name="myImage" - accept="image/*, .doc, .docx, .pdf, .zip, .pdf, .txt, .odt, .ods, .xls, .xlsx, .ppt, .pptx" + accept=" + image/*, .doc, .docx, .zip, .pdf, .txt, .odt, .ods, .html, + .xls, .xlsx, .ppt, .pptx, .jar, .gzip, .exe, .deb, .rar, .log, + .sh, .dmg, .pkg, .7z, .gz, .psd, .mp4, .rpm, .snap, .AppImage + " >
@@ -295,7 +299,7 @@ class ChatTextEditor extends LitElement { } sendMessageFunc(props) { - if (this.editor.isEmpty && (this.iframeId !== 'newChat' && this.iframeId !== 'newAttachmentChat')) return + if (this.editor.isEmpty && (this.iframeId !== 'newChat' && this.iframeId !== 'newGifChat' && this.iframeId !== 'newAttachmentChat' && this.iframeId !== 'newFileChat')) return this.getMessageSize(this.editor.getJSON()) @@ -351,18 +355,42 @@ class ChatTextEditor extends LitElement { repliedTo: '', version: 3 } + } else if (this.gifFile && this.iframeId === 'newGifChat') { + messageObject = { + messageText: trimmedMessage, + images: [{ + service: "IMAGE", + name: '123456789123456789123456789', + identifier: '123456' + }], + repliedTo: '', + version: 3 + } } else if (this.attachment && this.iframeId === 'newAttachmentChat') { messageObject = { messageText: trimmedMessage, attachments: [{ - service: "QCHAT_ATTACHMENT", + service: "ATTACHMENT", name: '123456789123456789123456789', identifier: '123456', attachmentName: "123456789123456789123456789", attachmentSize: "123456" }], repliedTo: '', - version: 2 + version: 3 + } + } else if (this.appFile && this.iframeId === 'newFileChat') { + messageObject = { + messageText: trimmedMessage, + files: [{ + service: "FILE", + name: '123456789123456789123456789', + identifier: '123456', + appFileName: "123456789123456789123456789", + appFileSize: "123456" + }], + repliedTo: '', + version: 3 } } else { messageObject = { diff --git a/plugins/plugins/core/components/plugins-css.js b/plugins/plugins/core/components/plugins-css.js index 6ee176c2..b5c570db 100644 --- a/plugins/plugins/core/components/plugins-css.js +++ b/plugins/plugins/core/components/plugins-css.js @@ -426,7 +426,6 @@ export const chatpageStyles = css` width: 800px; } - .close-icon { color: #676b71; width: 18px; @@ -1152,6 +1151,22 @@ export const chatpageStyles = css` width: 70%; } + .file-icon-container { + display: flex; + align-items: center; + justify-content: center; + height: 128px; + width: 128px; + border-radius: 50%; + border: none; + background-color: transparent; + } + + .file-icon { + height: 128px; + width: 128px; + } + .attachment-name { font-family: Work Sans, sans-serif; font-size: 20px; @@ -1440,6 +1455,7 @@ export const chatStyles = css` --mdc-theme-secondary: var(--mdc-theme-primary); --mdc-dialog-max-width: 85vw; --mdc-dialog-max-height: 95vh; + } * :focus-visible { @@ -1567,7 +1583,6 @@ export const chatStyles = css` min-width: 150px; } - .message-myBg { background-color: var(--chat-bubble-myBg) !important; } @@ -2107,10 +2122,8 @@ export const chatStyles = css` justify-content: space-evenly; padding: 5px 0 10px 0; gap: 20px; - cursor: pointer; } - .attachment-icon-container { display: flex; align-items: center; @@ -2126,6 +2139,30 @@ export const chatStyles = css` width: 70%; } + .file-container { + display: flex; + align-items: center; + justify-content: space-evenly; + padding: 5px 0 10px 0; + gap: 20px; + } + + .file-icon-container { + display: flex; + align-items: center; + justify-content: center; + height: 50px; + width: 50px; + border-radius: 50%; + border: none; + background-color: transparent; + } + + .file-icon { + height: 50px; + width: 50px; + } + .attachment-info { display: flex; flex-direction: column; @@ -2158,6 +2195,7 @@ export const chatStyles = css` color: var(--chat-bubble-msg-color); width: 19px; background-color: transparent; + cursor: pointer; } .download-icon:hover::before { @@ -2247,6 +2285,155 @@ export const chatStyles = css` transform: rotate(360deg); } } + + paper-dialog.progress { + width: auto; + max-width: 50vw; + height: auto; + max-height: 30vh; + background-color: var(--white); + color: var(--black); + border: 1px solid var(--black); + border-radius: 15px; + text-align: center; + padding: 15px; + line-height: 1.6; + overflow: hidden; + } + + paper-dialog.close-progress { + min-width: 550px; + max-width: 550px; + height: auto; + background-color: var(--white); + color: var(--black); + border: 1px solid var(--black); + border-radius: 15px; + text-align: center; + padding: 15px; + font-size: 17px; + font-weight: 500; + line-height: 20px; + overflow: hidden; + } + + .lds-roller { + display: inline-block; + position: relative; + width: 80px; + height: 80px; + } + + .lds-roller div { + animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite; + transform-origin: 40px 40px; + } + + .lds-roller div:after { + content: " "; + display: block; + position: absolute; + width: 7px; + height: 7px; + border-radius: 50%; + background: var(--black); + margin: -4px 0 0 -4px; + } + + .lds-roller div:nth-child(1) { + animation-delay: -0.036s; + } + + .lds-roller div:nth-child(1):after { + top: 63px; + left: 63px; + } + + .lds-roller div:nth-child(2) { + animation-delay: -0.072s; + } + + .lds-roller div:nth-child(2):after { + top: 68px; + left: 56px; + } + + .lds-roller div:nth-child(3) { + animation-delay: -0.108s; + } + + .lds-roller div:nth-child(3):after { + top: 71px; + left: 48px; + } + + .lds-roller div:nth-child(4) { + animation-delay: -0.144s; + } + + .lds-roller div:nth-child(4):after { + top: 72px; + left: 40px; + } + + .lds-roller div:nth-child(5) { + animation-delay: -0.18s; + } + + .lds-roller div:nth-child(5):after { + top: 71px; + left: 32px; + } + + .lds-roller div:nth-child(6) { + animation-delay: -0.216s; + } + + .lds-roller div:nth-child(6):after { + top: 68px; + left: 24px; + } + + .lds-roller div:nth-child(7) { + animation-delay: -0.252s; + } + + .lds-roller div:nth-child(7):after { + top: 63px; + left: 17px; + } + + .lds-roller div:nth-child(8) { + animation-delay: -0.288s; + } + + .lds-roller div:nth-child(8):after { + top: 56px; + left: 12px; + } + + @keyframes lds-roller { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } + } + + .close-download { + color: var(--black); + font-size: 14px; + font-weight: bold; + position: absolute; + top: -15px; + right: -15px; + } + + .close-download:hover { + color: #df3636; + } ` export const toolTipStyles = css` @@ -3631,6 +3818,12 @@ export const chatTextEditorStyles = css` cursor: pointer; } + .ProseMirror { + word-wrap: break-word; + white-space: pre-wrap; + white-space: break-spaces; + } + .ProseMirror:focus { outline: none; }