diff --git a/img/attachment-icon.png b/img/attachment-icon.png new file mode 100644 index 00000000..cb43410d Binary files /dev/null and b/img/attachment-icon.png differ diff --git a/qortal-ui-core/font/WorkSans.ttf b/qortal-ui-core/font/WorkSans.ttf new file mode 100644 index 00000000..c8d05412 Binary files /dev/null and b/qortal-ui-core/font/WorkSans.ttf differ diff --git a/qortal-ui-core/font/material-icons.css b/qortal-ui-core/font/material-icons.css index 1af6891e..881a68be 100644 --- a/qortal-ui-core/font/material-icons.css +++ b/qortal-ui-core/font/material-icons.css @@ -14,9 +14,9 @@ @font-face { font-family: 'Material Symbols Outlined'; font-style: normal; - src: local('MaterialSymbolsOutlined'), - url(MaterialSymbolsOutlined.ttf) format('truetype'), - url(MaterialSymbolsOutlined.woff2) format('woff2') + src: local('MaterialSymbolsOutlined'), + url(MaterialSymbolsOutlined.ttf) format('truetype'), + url(MaterialSymbolsOutlined.woff2) format('woff2') } @font-face { @@ -26,6 +26,13 @@ url(Montserrat.ttf) format('truetype'); } +@font-face { + font-family: 'WorkSans'; + src: local('WorkSans'), + local('WorkSans'), + url(WorkSans.ttf) format('truetype'); +} + @font-face { font-family: 'Raleway'; src: local('Raleway'), @@ -77,7 +84,8 @@ font-family: 'Material Symbols Outlined'; font-weight: normal; font-style: normal; - font-size: 24px; /* Preferred icon size */ + font-size: 24px; + /* Preferred icon size */ display: inline-block; line-height: 1; text-transform: none; diff --git a/qortal-ui-core/language/us.json b/qortal-ui-core/language/us.json index baeae94e..819b2454 100644 --- a/qortal-ui-core/language/us.json +++ b/qortal-ui-core/language/us.json @@ -570,7 +570,10 @@ "cchange61": "Error when fetching group invites. Please try again!", "cchange62": "Wrong Username and Address Inputted! Please try again!", "cchange63": "Enter Enabled", - "cchange64": "Enter Disabled" + "cchange64": "Enter Disabled", + "cchange65": "Uploading attachment. This may take up to one minute.", + "cchange66": "Deleting attachment. This may take up to one minute.", + "cchange67": "Attachment size exceeds 1 MB" }, "welcomepage": { "wcchange1": "Welcome to Q-Chat", diff --git a/qortal-ui-plugins/package.json b/qortal-ui-plugins/package.json index 0f04dae9..da44ec3b 100644 --- a/qortal-ui-plugins/package.json +++ b/qortal-ui-plugins/package.json @@ -21,15 +21,17 @@ "@material/mwc-list": "0.27.0", "@material/mwc-select": "0.27.0", "@tiptap/core": "2.0.0-beta.209", + "@tiptap/extension-highlight": "2.0.0-beta.209", "@tiptap/extension-image": "2.0.0-beta.209", "@tiptap/extension-placeholder": "2.0.0-beta.209", "@tiptap/extension-underline": "2.0.0-beta.209", - "@tiptap/extension-highlight": "2.0.0-beta.209", "@tiptap/html": "2.0.0-beta.209", "@tiptap/starter-kit": "2.0.0-beta.209", "asmcrypto.js": "2.3.2", + "axios": "1.2.3", "compressorjs": "1.1.1", "emoji-picker-js": "https://github.com/Qortal/emoji-picker-js", + "localforage": "1.10.0", "prosemirror-commands": "1.5.0", "prosemirror-dropcursor": "1.6.1", "prosemirror-gapcursor": "1.3.1", @@ -40,7 +42,6 @@ "prosemirror-state": "1.4.2", "prosemirror-transform": "1.7.0", "prosemirror-view": "1.29.1", - "localforage": "1.10.0", "short-unique-id": "4.4.4" }, "devDependencies": { diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index f630b65a..05d76dd3 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -39,7 +39,7 @@ import { replaceMessagesEdited } from '../../utils/replace-messages-edited.js'; import { publishData } from '../../utils/publish-image.js'; import { EmojiPicker } from 'emoji-picker-js'; import WebWorker from 'web-worker:./computePowWorker.js'; -import WebWorkerImage from 'web-worker:./computePowWorkerImage.js'; +import WebWorkerFile from 'web-worker:./computePowWorkerFile.js'; import '@polymer/paper-dialog/paper-dialog.js' // const messagesCache = localForage.createInstance({ @@ -77,7 +77,9 @@ class ChatPage extends LitElement { editedMessageObj: { type: Object }, iframeHeight: { type: Number }, imageFile: { type: Object }, + attachment: { type: Object }, isUploadingImage: { type: Boolean }, + isUploadingAttachment: { type: Boolean }, userLanguage: { type: String }, lastMessageRefVisible: { type: Boolean }, isLoadingOldMessages: { type: Boolean }, @@ -94,7 +96,7 @@ class ChatPage extends LitElement { userFound: { type: Array }, userFoundModalOpen: { type: Boolean }, webWorker: { type: Object }, - webWorkerImage: { type: Object }, + webWorkerFile: { type: Object }, myTrimmedMeassage: { type: String }, editor: {type: Object}, currentEditor: {type: String}, @@ -822,6 +824,30 @@ class ChatPage extends LitElement { cursor: pointer; color: #4e5054; } + + .attachment-icon-container { + display: flex; + align-items: center; + justify-content: center; + height: 120px; + width: 120px; + border-radius: 50%; + border: none; + background-color: var(--mdc-theme-primary); + } + + .attachment-icon { + width: 70%; + } + + .attachment-name { + font-family: Work Sans, sans-serif; + font-size: 20px; + color: var(--chat-bubble-msg-color); + margin: 0px; + letter-spacing: 1px; + padding: 5px 0px; + } ` } @@ -829,7 +855,7 @@ class ChatPage extends LitElement { super() this.getOldMessage = this.getOldMessage.bind(this) this._sendMessage = this._sendMessage.bind(this) - this.insertImage = this.insertImage.bind(this) + this.insertFile = this.insertFile.bind(this) this.toggleEnableChatEnter = this.toggleEnableChatEnter.bind(this) this._downObserverhandler = this._downObserverhandler.bind(this) this.setOpenTipUser = this.setOpenTipUser.bind(this) @@ -859,6 +885,7 @@ class ChatPage extends LitElement { this.editedMessageObj = null this.iframeHeight = 42 this.imageFile = null + this.attachment = null this.uid = new ShortUniqueId() this.userLanguage = "" this.lastMessageRefVisible = false @@ -886,7 +913,7 @@ class ChatPage extends LitElement { selected: false } this.webWorker = null; - this.webWorkerImage = null; + this.webWorkerFile = null; this.currentEditor = '_chatEditorDOM' this.initialChat = this.initialChat.bind(this) this.isEnabledChatEnter = true @@ -906,7 +933,6 @@ class ChatPage extends LitElement { } setUserName(props) { - console.log({props}) this.userName = props.senderName ? props.senderName : props.sender; this.setSelectedHead(props); } @@ -925,7 +951,6 @@ class ChatPage extends LitElement { } render() { - console.log(this.userName, "username here"); return html`
-

${translate("chatpage.cchange25")}

- ${unsafeHTML(generateHTML(this.editedMessageObj.message, [ - StarterKit, - Underline, - Highlight - // other extensions … - ]))} +

+ ${translate("chatpage.cchange25")} +

+ ${unsafeHTML(generateHTML(this.editedMessageObj.message, + [StarterKit, + Underline, + Highlight + // other extensions … + ]))}
this.closeEditMessageContainer()} - > + @click=${() => this.closeEditMessageContainer()}> +
`} @@ -1030,7 +1057,7 @@ class ChatPage extends LitElement { placeholder=${this.chatEditorPlaceholder} ._sendMessage=${this._sendMessage} .imageFile=${this.imageFile} - .insertImage=${this.insertImage} + .insertFile=${this.insertFile} .editedMessageObj=${this.editedMessageObj} ?isLoading=${this.isLoading} ?isLoadingMessages=${this.isLoadingMessages} @@ -1047,25 +1074,38 @@ class ChatPage extends LitElement { - ${(this.isUploadingImage || this.isDeletingImage) ? html` + ${(this.isUploadingImage || this.isDeletingImage) ? html`
-

- ${this.isDeletingImage ? - translate("chatpage.cchange31") : translate("chatpage.cchange30")} -

-
-
-
- - `: ''} +

+ ${this.isDeletingImage ? + translate("chatpage.cchange31") : translate("chatpage.cchange30")} +

+ + + + + `: ''} + ${(this.isUploadingAttachment) ? html` +
+
+
+
+

+ ${translate("chatpage.cchange65")} +

+
+
+
+ + `: ''} { this.removeImage(); }} - style=${(this.imageFile && !this.isUploadingImage) ? "visibility:visible;z-index:50" : "visibility: hidden;z-index:-100"}> + style=${(this.imageFile && !this.isUploadingImage) ? "visibility:visible; z-index:50" : "visibility: hidden;z-index:-100"}>
${this.imageFile && html` @@ -1078,7 +1118,7 @@ class ChatPage extends LitElement { placeholder=${this.chatEditorPlaceholder} ._sendMessage=${this._sendMessage} .imageFile=${this.imageFile} - .insertImage=${this.insertImage} + .insertFile=${this.insertFile} .editedMessageObj=${this.editedMessageObj} ?isLoading=${this.isLoading} ?isLoadingMessages=${this.isLoadingMessages} @@ -1101,7 +1141,6 @@ class ChatPage extends LitElement { const chatTextEditor = this.shadowRoot.getElementById('chatTextCaption') chatTextEditor.sendMessageFunc({ type: 'image', - imageFile: this.imageFile, }) }} > @@ -1111,6 +1150,58 @@ class ChatPage extends LitElement {
+ { + this.removeAttachment(); + }} + style=${this.attachment && !this.isUploadingAttachment ? "visibility: visible; z-index: 50" : "visibility: hidden; z-index: -100"}> +
+
+ ${this.attachment && html` +
+ attachment-icon +
+ `} +

${this.attachment && this.attachment.name}

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

${translate("chatpage.cchange41")}


@@ -1332,14 +1423,15 @@ class ChatPage extends LitElement { - async connectedCallback() { + async connectedCallback() { super.connectedCallback(); this.webWorker = new WebWorker(); - this.webWorkerImage = new WebWorkerImage(); + this.webWorkerFile = new WebWorkerFile(); await this.getUpdateCompleteTextEditor(); - const elementChatId = this.shadowRoot.getElementById('_chatEditorDOM').shadowRoot.getElementById('_chatEditorDOM') - const elementChatImageId = this.shadowRoot.getElementById('chatTextCaption').shadowRoot.getElementById('newChat') + const elementChatId = this.shadowRoot.getElementById('_chatEditorDOM').shadowRoot.getElementById('_chatEditorDOM'); + const elementChatImageId = this.shadowRoot.getElementById('chatTextCaption').shadowRoot.getElementById('newChat'); + const elementChatAttachmentId = this.shadowRoot.getElementById('chatAttachmentId').shadowRoot.getElementById('newAttachmentChat'); this.editor = new Editor({ onUpdate: ()=> { this.shadowRoot.getElementById('_chatEditorDOM').getMessageSize(this.editor.getJSON()) @@ -1396,11 +1488,37 @@ class ChatPage extends LitElement { Extension.create({ addKeyboardShortcuts:()=> { return { - 'Enter':()=> { + 'Enter':() => { const chatTextEditor = this.shadowRoot.getElementById('chatTextCaption') chatTextEditor.sendMessageFunc({ - type: 'image', - imageFile: this.imageFile, + type: 'image' + }) + return true + } + } + }}) + ] + }) + + this.editorAttachment = new Editor({ + onUpdate: () => { + this.shadowRoot.getElementById('chatAttachmentId').getMessageSize(this.editorAttachment.getJSON()) + }, + element: elementChatAttachmentId, + extensions: [ + StarterKit, + Underline, + Highlight, + Placeholder.configure({ + placeholder: 'Write something …', + }), + Extension.create({ + addKeyboardShortcuts:()=> { + return { + 'Enter':()=> { + const chatTextEditor = this.shadowRoot.getElementById('chatAttachmentId') + chatTextEditor.sendMessageFunc({ + type: 'attachment' }) return true } @@ -1411,17 +1529,18 @@ class ChatPage extends LitElement { document.addEventListener('keydown', this.initialChat); } - disconnectedCallback() { - super.disconnectedCallback(); - this.webWorker.terminate(); - this.webWorkerImage.terminate(); - this.editor.destroy() - this.editorImage.destroy() - document.removeEventListener('keydown', this.initialChat); - } + disconnectedCallback() { + super.disconnectedCallback(); + this.webWorker.terminate(); + this.webWorkerFile.terminate(); + this.editor.destroy(); + this.editorImage.destroy(); + this.editorAttachment.destroy(); + document.removeEventListener('keydown', this.initialChat); + } initialChat(e) { - if (this.editor && !this.editor.isFocused && this.currentEditor === '_chatEditorDOM' && !this.openForwardOpen && !this.openTipUser) { + if (this.editor && !this.editor.isFocused && this.currentEditor === '_chatEditorDOM' && !this.openForwardOpen && !this.openTipUser) { // WARNING: Deprecated methods from KeyBoard Event if (e.code === "Space" || e.keyCode === 32 || e.which === 32) { } else if (inputKeyCodes.includes(e.keyCode)) { @@ -1431,8 +1550,6 @@ class ChatPage extends LitElement { this.editor.commands.focus('end') } } - - } async userSearch() { @@ -1498,19 +1615,29 @@ class ChatPage extends LitElement { } - insertImage(file) { + insertFile(file) { if (file.type.includes('image')) { this.imageFile = file; - this.currentEditor = 'newChat' + this.currentEditor = 'newChat'; return; - } - parentEpml.request('showSnackBar', get("chatpage.cchange28")); + } else { + this.attachment = file; + this.currentEditor = "newAttachmentChat"; + return; + } + // parentEpml.request('showSnackBar', get("chatpage.cchange28")); } removeImage() { this.imageFile = null; - this.resetChatEditor() - this.currentEditor = '_chatEditorDOM' + this.resetChatEditor(); + this.currentEditor = '_chatEditorDOM'; + } + + removeAttachment() { + this.attachment = null; + this.resetChatEditor(); + this.currentEditor = '_chatEditorDOM'; } changeMsgInput(id) { @@ -1616,7 +1743,6 @@ class ChatPage extends LitElement { this.groupAdmin = membersAdminsWithName this.groupMembers = membersWithName this.groupInfo = getGroupInfo - console.log({membersAdminsWithName}) } catch (error) { console.error(error) } @@ -1678,7 +1804,6 @@ class ChatPage extends LitElement { this.editor.setEditable(true) } } - } async getName (recipient) { @@ -2002,8 +2127,6 @@ class ChatPage extends LitElement { let decodedMessageObj = {}; if (isReceipientVar === true) { - console.log('encoded', encodedMessageObj.isEncrypted, _publicKeyVar.hasPubKey,encodedMessageObj.data) - // direct chat if (encodedMessageObj.isEncrypted === true && _publicKeyVar.hasPubKey === true && encodedMessageObj.data) { let decodedMessage = window.parent.decryptChatMessage(encodedMessageObj.data, window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey, _publicKeyVar.key, encodedMessageObj.reference); decodedMessageObj = { ...encodedMessageObj, decodedMessage }; @@ -2215,6 +2338,9 @@ class ChatPage extends LitElement { if(this.currentEditor === 'newChat'){ this.editorImage.commands.setContent('') } + if(this.currentEditor === 'newAttachmentChat'){ + this.editorAttachment.commands.setContent('') + } } async _sendMessage(outSideMsg, msg) { @@ -2258,6 +2384,7 @@ class ChatPage extends LitElement { // find specific object property in local let typeMessage = 'regular'; let workerImage; + let workerAttachment; this.isLoading = true; const trimmedMessage = msg @@ -2286,10 +2413,10 @@ class ChatPage extends LitElement { let compressedFile = '' var str = "iVBORw0KGgoAAAANSUhEUgAAAsAAAAGMAQMAAADuk4YmAAAAA1BMVEX///+nxBvIAAAAAXRSTlMAQObYZgAAADlJREFUeF7twDEBAAAAwiD7p7bGDlgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAGJrAABgPqdWQAAAABJRU5ErkJggg=="; - if (this.webWorkerImage) { - workerImage = this.webWorkerImage; + if (this.webWorkerFile) { + workerImage = this.webWorkerFile; } else { - this.webWorkerImage = new WebWorkerImage(); + this.webWorkerFile = new WebWorkerFile(); } const b64toBlob = (b64Data, contentType='', sliceSize=512) => { @@ -2367,7 +2494,6 @@ class ChatPage extends LitElement { } const stringifyMessageObject = JSON.stringify(messageObject); this.sendMessage(stringifyMessageObject, typeMessage, chatReference); - } else if (outSideMsg && outSideMsg.type === 'image') { @@ -2379,10 +2505,10 @@ class ChatPage extends LitElement { return; } - if (this.webWorkerImage) { - workerImage = this.webWorkerImage; + if (this.webWorkerFile) { + workerImage = this.webWorkerFile; } else { - this.webWorkerImage = new WebWorkerImage(); + this.webWorkerFile = new WebWorkerFile(); } const image = this.imageFile @@ -2412,41 +2538,101 @@ class ChatPage extends LitElement { this.isUploadingImage = false; return; } - try { - await publishData({ - registeredName: userName, - file : compressedFile, - service: 'QCHAT_IMAGE', - identifier : identifier, - parentEpml, - metaData: undefined, - uploadType: 'file', - selectedAddress: this.selectedAddress, - worker: workerImage - }); + try { + await publishData({ + registeredName: userName, + file : compressedFile, + service: 'QCHAT_IMAGE', + identifier : identifier, + parentEpml, + metaData: undefined, + uploadType: 'file', + selectedAddress: this.selectedAddress, + worker: workerImage + }); + this.isUploadingImage = false; + this.removeImage(); + } catch (error) { + console.error(error); + this.isLoading = false; this.isUploadingImage = false; - this.removeImage() - } catch (error) { - this.isLoading = false; - this.isUploadingImage = false; - return; - } + return; + } - - - const messageObject = { - messageText: trimmedMessage, - images: [{ - service: "QCHAT_IMAGE", - name: userName, - identifier: identifier - }], - isImageDeleted: false, - repliedTo: '', - version: 2 - }; - const stringifyMessageObject = JSON.stringify(messageObject); - this.sendMessage(stringifyMessageObject, typeMessage); + const messageObject = { + messageText: trimmedMessage, + images: [{ + service: "QCHAT_IMAGE", + name: userName, + identifier: identifier + }], + isImageDeleted: false, + repliedTo: '', + version: 2 + }; + const stringifyMessageObject = JSON.stringify(messageObject); + this.sendMessage(stringifyMessageObject, typeMessage); + } + else if (outSideMsg && outSideMsg.type === 'attachment') { + this.isUploadingAttachment = true; + const userName = await getName(this.selectedAddress.address); + if (!userName) { + parentEpml.request('showSnackBar', get("chatpage.cchange27")); + this.isLoading = false; + return; + } + + if (this.webWorkerFile) { + workerAttachment = this.webWorkerFile; + } else { + this.webWorkerFile = new WebWorkerFile(); + } + + const attachment = this.attachment; + const id = this.uid(); + const identifier = `qchat_${id}`; + const fileSize = attachment.size; + if (fileSize > 1000000) { + parentEpml.request('showSnackBar', get("chatpage.cchange67")); + this.isLoading = false; + this.isUploadingAttachment = false; + return; + } + try { + await publishData({ + registeredName: userName, + file : attachment, + service: 'QCHAT_ATTACHMENT', + identifier : identifier, + parentEpml, + metaData: undefined, + uploadType: 'file', + selectedAddress: this.selectedAddress, + worker: workerAttachment + }); + this.isUploadingAttachment = false; + this.removeAttachment(); + } catch (error) { + console.error(error); + this.isLoading = false; + this.isUploadingAttachment = false; + return; + } + const messageObject = { + messageText: trimmedMessage, + attachments: [{ + service: 'QCHAT_ATTACHMENT', + name: userName, + identifier: identifier, + attachmentName: attachment.name, + attachmentSize: attachment.size + }], + isAttachmentDeleted: false, + repliedTo: '', + version: 2 + }; + const stringifyMessageObject = JSON.stringify(messageObject); + this.sendMessage(stringifyMessageObject, typeMessage); } else if (outSideMsg && outSideMsg.type === 'reaction') { typeMessage = 'edit'; let chatReference = outSideMsg.editedMessageObj.reference; diff --git a/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js b/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js index 2c62a563..4be2d907 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js +++ b/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js @@ -612,8 +612,9 @@ export const chatStyles = css` font-family: 'JetBrainsMono', monospace; padding: 0.75rem 1rem; border-radius: 0.5rem; - white-space: pre-wrap; + white-space: pre-wrap; } + .replied-message pre code { color: inherit; padding: 0; @@ -621,12 +622,10 @@ export const chatStyles = css` font-size: 0.8rem; } - .replied-message img { width: 1.7em; height: 1.5em; margin: 0px; - } .replied-message blockquote { @@ -639,4 +638,77 @@ export const chatStyles = css` border-top: 2px solid rgba(#0D0D0D, 0.1); margin: 2rem 0; } + + .attachment-container { + display: flex; + align-items: center; + justify-content: space-evenly; + padding: 5px 0 10px 0; + gap: 20px; + cursor: pointer; + } + + .attachment-container:hover .download-icon::before { + background-color: rgb(161 158 158 / 41%); + } + + + + .attachment-icon-container { + display: flex; + align-items: center; + justify-content: center; + height: 50px; + width: 50px; + border-radius: 50%; + border: none; + background-color: var(--mdc-theme-primary); + } + + .attachment-icon { + width: 70%; + } + + .attachment-info { + display: flex; + flex-direction: column; + gap: 5px; + } + + .attachment-name { + font-family: Work Sans, sans-serif; + font-size: 16px; + color: var(--chat-bubble-msg-color); + margin: 0; + letter-spacing: 0.4px; + padding: 5px 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .attachment-size { + font-family: Roboto, sans-serif; + font-size: 16px; + color: var(--chat-bubble-msg-color); + margin: 0; + letter-spacing: 0.3px; + font-weight: 300; + } + + .download-icon { + position: relative; + color: var(--chat-bubble-msg-color); + width: 19px; + background-color: transparent; + } + + .download-icon::before { + content: ""; + position: absolute; + border-radius: 50%; + padding: 18px; + background-color: transparent; + transition: all 0.3s ease-in-out; + } ` diff --git a/qortal-ui-plugins/plugins/core/components/ChatScroller.js b/qortal-ui-plugins/plugins/core/components/ChatScroller.js index e9db4e5e..3eaecb94 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatScroller.js +++ b/qortal-ui-plugins/plugins/core/components/ChatScroller.js @@ -6,6 +6,7 @@ import {unsafeHTML} from 'lit/directives/unsafe-html.js'; import { chatStyles } from './ChatScroller-css.js' import { Epml } from "../../../epml"; import { cropAddress } from "../../utils/cropAddress"; +import { roundToNearestDecimal } from '../../utils/roundToNearestDecimal.js'; import './LevelFounder.js'; import './NameMenu.js'; import './ChatModals.js'; @@ -17,7 +18,9 @@ import '@material/mwc-button'; import '@material/mwc-dialog'; import '@material/mwc-icon'; import { EmojiPicker } from 'emoji-picker-js'; -import { generateHTML } from '@tiptap/core' +import { generateHTML } from '@tiptap/core'; +import { saveAs } from 'file-saver'; +import axios from "axios"; import StarterKit from '@tiptap/starter-kit' import Underline from '@tiptap/extension-underline'; import Highlight from '@tiptap/extension-highlight' @@ -50,7 +53,7 @@ class ChatScroller extends LitElement { openTipUser: { type: Boolean }, openUserInfo: { type: Boolean }, userName: { type: String }, - selectedHead: { type: Object } + selectedHead: { type: Object }, } } @@ -270,7 +273,7 @@ class MessageTemplate extends LitElement { setOpenTipUser: { attribute: false }, setOpenUserInfo: { attribute: false }, setUserName: { attribute: false }, - openTipUser:{ type: Boolean } + openTipUser:{ type: Boolean }, } } @@ -318,6 +321,22 @@ class MessageTemplate extends LitElement { } } + async 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; + try{ + const res = await axios.get(`${nodeUrl}/arbitrary/QCHAT_ATTACHMENT/${attachment.name}/${attachment.identifier}`, { responseType: 'blob' }) + .then(response =>{ + let filename = attachment.attachmentName; + console.log({response: response.data}); + let blob = new Blob([response.data], { type:"application/octet-stream" }); + saveAs(blob , filename); + }) + } catch (error) { + console.error(error); + } + } + render() { const hidemsg = this.hideMessages; let message = ""; @@ -328,8 +347,10 @@ class MessageTemplate extends LitElement { let isImageDeleted = false; let version = 0; let isForwarded = false + let attachment = null; try { const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage); + console.log({parsedMessageObj}); if(parsedMessageObj.version.toString() === '2'){ messageVersion2 = generateHTML(parsedMessageObj.messageText, [ @@ -345,6 +366,10 @@ class MessageTemplate extends LitElement { reactions = parsedMessageObj.reactions || []; version = parsedMessageObj.version isForwarded = parsedMessageObj.type === 'forward' + if (parsedMessageObj.attachments && Array.isArray(parsedMessageObj.attachments) && parsedMessageObj.attachments.length > 0) { + attachment = parsedMessageObj.attachments[0]; + } + console.log({parsedMessageObj}); if (parsedMessageObj.images && Array.isArray(parsedMessageObj.images) && parsedMessageObj.images.length > 0) { image = parsedMessageObj.images[0]; } @@ -403,12 +428,11 @@ class MessageTemplate extends LitElement { const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port; imageUrl = `${nodeUrl}/arbitrary/${image.service}/${image.name}/${image.identifier}?async=true&apiKey=${myNode.apiKey}`; - if(this.viewImage || this.myAddress === this.messageObj.sender){ + if (this.viewImage || this.myAddress === this.messageObj.sender) { imageHTML = createImage(imageUrl); imageHTMLDialog = createImage(imageUrl) imageHTMLDialog.style= "height: auto; max-height: 80vh; width: auto; max-width: 80vw; object-fit: contain; border-radius: 5px"; } - } nameMenu = html` @@ -529,12 +553,12 @@ class MessageTemplate extends LitElement { ${repliedToData.decodedMessage.messageText} ` : ''} ${version.toString() === '2' ? html` - ${unsafeHTML(generateHTML(repliedToData.decodedMessage.messageText, [ - StarterKit, - Underline, - Highlight - // other extensions … - ]))} + ${unsafeHTML(generateHTML(repliedToData.decodedMessage.messageText, + [StarterKit, + Underline, + Highlight + // other extensions … + ]))} ` : ''} @@ -548,10 +572,9 @@ class MessageTemplate extends LitElement { }} class=${[`image-container`, !this.isImageLoaded ? 'defaultSize' : ''].join(' ')} style=${this.isFirstMessage && "margin-top: 10px;"}> -
- ${translate("chatpage.cchange40")} -
- +
+ ${translate("chatpage.cchange40")} +
` : html``} ${image && !isImageDeleted && (this.viewImage || this.myAddress === this.messageObj.sender) ? html` @@ -561,13 +584,37 @@ class MessageTemplate extends LitElement { ${imageHTML} { this.openDeleteImage = true; - this.chatE }} class="image-delete-icon" icon="vaadin:close" slot="icon"> ` : image && isImageDeleted ? html`

This image has been deleted

` : html``} + ${attachment ? + html` +
await this.downloadAttachment(attachment)} class="attachment-container"> +
+ attachment-icon +
+
+

+ ${attachment && attachment.attachmentName} +

+

+ ${roundToNearestDecimal(attachment.attachmentSize)} mb +

+
+ + +
+ ` + : html``}
* + * { - margin-top: 0.75em; - outline: none; + .ProseMirror > * + * { + margin-top: 0.75em; + outline: none; } - .ProseMirror ul, - ol { - padding: 0 1rem; - } - - .ProseMirror h1, - h2, - h3, - h4, - h5, - h6 { - line-height: 1.1; - } - - .ProseMirror code { - background-color: rgba(#616161, 0.1); - color: #616161; - } - - .ProseMirror pre { - background: #0D0D0D; - color: #FFF; - font-family: 'JetBrainsMono', monospace; - padding: 0.75rem 1rem; - border-radius: 0.5rem; - white-space: pre-wrap; - } - .ProseMirror pre code { - color: inherit; - padding: 0; - background: none; - font-size: 0.8rem; + .ProseMirror ul, + ol { + padding: 0 1rem; } + .ProseMirror h1, + h2, + h3, + h4, + h5, + h6 { + line-height: 1.1; + } - .ProseMirror img { - width: 1.7em; - height: 1.5em; - margin: 0px; + .ProseMirror code { + background-color: rgba(#616161, 0.1); + color: #616161; + } - } + .ProseMirror pre { + background: #0D0D0D; + color: #FFF; + font-family: 'JetBrainsMono', monospace; + padding: 0.75rem 1rem; + border-radius: 0.5rem; + white-space: pre-wrap; + } + .ProseMirror pre code { + color: inherit; + padding: 0; + background: none; + font-size: 0.8rem; + } - .ProseMirror blockquote { - padding-left: 1rem; - border-left: 2px solid rgba(#0D0D0D, 0.1); - } - .ProseMirror hr { - border: none; - border-top: 2px solid rgba(#0D0D0D, 0.1); - margin: 2rem 0; - } - .chatbar-button-single { - background: var(--white); - outline: none; - border: none; - color: var(--black); - padding: 4px; - border-radius: 5px; - cursor: pointer; - margin-right: 2px; - filter: brightness(100%); - transition: all 0.2s; - display: none; - } - .chatbar-button-single:hover { - filter: brightness(120%); - - } + .ProseMirror img { + width: 1.7em; + height: 1.5em; + margin: 0px; - .chatbar-buttons { - margin-bottom: 5px; - flex-shrink: 0; - } + } - .show-chatbar-buttons { - display: flex; - align-items: center; - justify-content: center; - } - :host(:hover) .chatbar-button-single { - - display: flex; - align-items: center; - justify-content: center; - } - .ProseMirror p.is-editor-empty:first-child::before { - color: #adb5bd; - content: attr(data-placeholder); - float: left; - height: 0; - pointer-events: none; -} -.ProseMirror p { - font-size: 18px; - margin-block-start: 0px; - margin-block-end: 0px; - overflow-wrap: anywhere; -} + .ProseMirror blockquote { + padding-left: 1rem; + border-left: 2px solid rgba(#0D0D0D, 0.1); + } -.ProseMirror { - width: 100%; - box-sizing: border-box; - word-break: break-all; -} + .ProseMirror hr { + border: none; + border-top: 2px solid rgba(#0D0D0D, 0.1); + margin: 2rem 0; + } + .chatbar-button-single { + background: var(--white); + outline: none; + border: none; + color: var(--black); + padding: 4px; + border-radius: 5px; + cursor: pointer; + margin-right: 2px; + filter: brightness(100%); + transition: all 0.2s; + display: none; + } + .chatbar-button-single:hover { + filter: brightness(120%); + + } -.ProseMirror mark { - background-color: #ffe066; - border-radius: 0.25em; - box-decoration-break: clone; - padding: 0.125em 0; -} + .chatbar-buttons { + margin-bottom: 5px; + flex-shrink: 0; + } -.material-icons { - font-family: 'Material Icons'; - font-weight: normal; - font-style: normal; - font-size: 24px; - /* Preferred icon size */ - display: inline-block; - line-height: 1; - text-transform: none; - letter-spacing: normal; - word-wrap: normal; - white-space: nowrap; - direction: ltr; + .show-chatbar-buttons { + display: flex; + align-items: center; + justify-content: center; + } + :host(:hover) .chatbar-button-single { + + display: flex; + align-items: center; + justify-content: center; + } + .ProseMirror p.is-editor-empty:first-child::before { + color: #adb5bd; + content: attr(data-placeholder); + float: left; + height: 0; + pointer-events: none; + } + .ProseMirror p { + font-size: 18px; + margin-block-start: 0px; + margin-block-end: 0px; + overflow-wrap: anywhere; + } -} + .ProseMirror { + width: 100%; + box-sizing: border-box; + word-break: break-all; + } -.material-symbols-outlined { - font-family: 'Material Symbols Outlined'; - font-weight: normal; - font-style: normal; - font-size: 18px; /* Preferred icon size */ - display: inline-block; - line-height: 1; - text-transform: none; - letter-spacing: normal; - word-wrap: normal; - white-space: nowrap; - direction: ltr; -} -.hide-styling { - display: none; -} - - ` + .ProseMirror mark { + background-color: #ffe066; + border-radius: 0.25em; + box-decoration-break: clone; + padding: 0.125em 0; + } + + .material-icons { + font-family: 'Material Icons'; + font-weight: normal; + font-style: normal; + font-size: 24px; + /* Preferred icon size */ + display: inline-block; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + + } + + .material-symbols-outlined { + font-family: 'Material Symbols Outlined'; + font-weight: normal; + font-style: normal; + font-size: 18px; /* Preferred icon size */ + display: inline-block; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + } + + .hide-styling { + display: none; + } + ` } constructor() { @@ -357,163 +360,159 @@ class ChatTextEditor extends LitElement { } render() { - return html`
- - - - - - - - -
+ @click=${() => this.editor.chain().focus().toggleBold().run()} + ?disabled=${ + this.editor && + !this.editor.can() + .chain() + .focus() + .toggleBold() + .run()} + class=${["chatbar-button-single", (this.editedMessageObj || this.repliedToMessageObj) && 'show-chatbar-buttons', this.editor && this.editor.isActive('bold') ? 'is-active' : ''].join(" ")}> + + + + + + + + +
+
+
- -
{ - this.preventUserSendingImage(e) - }}> - - -
- -
-
- -
- - ${this.editedMessageObj ? ( - html` -
- ${this.isLoading === false ? html` - { - this.sendMessageFunc(); - }} - > - - ` : - html` - - `} -
- ` - ) : - html` -
- ${this.isLoading === false ? html` - send-icon { - this.sendMessageFunc(); - }} - /> - ` : - html` - - `} -
- ` - } -
- ${this.chatMessageSize >= 750 ? - html` -
-
- ${`Your message size is of ${this.chatMessageSize} bytes out of a maximum of 1000`} -
-
- ` : - html``} + style=${this.iframeId === "privateMessage" ? "display: none" : "display: block"} + class="file-picker-container" + @click=${(e) => { + this.preventUserSendingImage(e) + }}> + + +
+ +
+ +
+ + ${this.editedMessageObj ? ( + html` +
+ ${this.isLoading === false ? html` + { + this.sendMessageFunc(); + }} + > + + ` : + html` + + `} +
+ ` + ) : + html` +
+ ${this.isLoading === false ? html` + send-icon { + this.sendMessageFunc(); + }} + /> + ` : + html` + + `} +
+ ` + } + + ${this.chatMessageSize >= 750 ? + html` +
+
+ ${`Your message size is of ${this.chatMessageSize} bytes out of a maximum of 1000`} +
+
+ ` : + html``} + ` } @@ -524,14 +523,7 @@ class ChatTextEditor extends LitElement { } } - - - - async firstUpdated() { - - - window.addEventListener('storage', () => { const checkTheme = localStorage.getItem('qortalTheme'); const chatbar = this.shadowRoot.querySelector('.element') @@ -548,7 +540,6 @@ class ChatTextEditor extends LitElement { this.emojiPickerHandler = this.shadowRoot.querySelector('.emoji-button'); this.mirrorChatInput = this.shadowRoot.getElementById('messageBox'); - this.chatMessageInput = this.shadowRoot.querySelector('.element') this.emojiPicker = new EmojiPicker({ style: "twemoji", @@ -590,10 +581,6 @@ class ChatTextEditor extends LitElement { if (changedProperties && changedProperties.has('placeholder') && this.updatePlaceholder && this.editor) { this.updatePlaceholder(this.editor, this.placeholder ) } - - if (changedProperties && changedProperties.has("imageFile")) { - this.chatMessageInput = "newChat"; - } } shouldUpdate(changedProperties) { @@ -603,7 +590,7 @@ class ChatTextEditor extends LitElement { } sendMessageFunc(props) { - if(this.editor.isEmpty) return + if(this.editor.isEmpty && (!this.imageFile || !this.attachment)) return this.getMessageSize(this.editor.getJSON()) if (this.chatMessageSize > 1000 ) { parentEpml.request('showSnackBar', get("chatpage.cchange29")); @@ -615,8 +602,7 @@ class ChatTextEditor extends LitElement { getMessageSize(message){ try { - - const trimmedMessage = message + const trimmedMessage = message; let messageObject = {}; if (this.repliedToMessageObj) { @@ -636,20 +622,33 @@ class ChatTextEditor extends LitElement { const parsedMessageObj = JSON.parse(this.editedMessageObj.decodedMessage); message = parsedMessageObj; } catch (error) { - message = this.messageObj.decodedMessage + message = this.messageObj.decodedMessage; } messageObject = { ...message, messageText: trimmedMessage, } - } else if(this.imageFile && this.iframeId === 'newChat') { + } else if (this.imageFile && this.iframeId === 'newChat') { messageObject = { messageText: trimmedMessage, images: [{ service: "QCHAT_IMAGE", name: '123456789123456789123456789', identifier: '123456' - }], + }], + repliedTo: '', + version: 2 + }; + } else if (this.attachment && this.iframeId === 'newAttachmentChat') { + messageObject = { + messageText: trimmedMessage, + attachments: [{ + service: "QCHAT_ATTACHMENT", + name: '123456789123456789123456789', + identifier: '123456', + attachmentName: "123456789123456789123456789", + attachmentSize: "123456" + }], repliedTo: '', version: 2 }; @@ -661,7 +660,6 @@ class ChatTextEditor extends LitElement { version: 2 }; } - const stringified = JSON.stringify(messageObject); const size = new Blob([stringified]).size; this.chatMessageSize = size; @@ -670,7 +668,6 @@ class ChatTextEditor extends LitElement { } } - } diff --git a/qortal-ui-plugins/plugins/core/components/computePowWorkerImage.js b/qortal-ui-plugins/plugins/core/components/computePowWorkerFile.js similarity index 100% rename from qortal-ui-plugins/plugins/core/components/computePowWorkerImage.js rename to qortal-ui-plugins/plugins/core/components/computePowWorkerFile.js diff --git a/qortal-ui-plugins/plugins/utils/roundToNearestDecimal.js b/qortal-ui-plugins/plugins/utils/roundToNearestDecimal.js new file mode 100644 index 00000000..5e3fe0aa --- /dev/null +++ b/qortal-ui-plugins/plugins/utils/roundToNearestDecimal.js @@ -0,0 +1,4 @@ +export function roundToNearestDecimal(num) { + const mb = num / 1000000; + return Math.round(mb * 10) / 10; +} \ No newline at end of file