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`
+
+
+
+ `}
+
${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 && 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.insertImage(e.target.files[0]);
- const filePickerInput = this.shadowRoot.getElementById('file-picker')
- if(filePickerInput){
- filePickerInput.value = ""
- }
- }
- }"
- id="file-picker"
- class="file-picker-input" type="file" name="myImage" accept="image/*" />
-
-
-
-
-
- ${this.editedMessageObj ? (
- html`
-
- ${this.isLoading === false ? html`
-
{
- this.sendMessageFunc();
- }}
- >
-
- ` :
- html`
-
- `}
-
- `
- ) :
- html`
-
- ${this.isLoading === false ? html`
-
{
- 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.insertFile(e.target.files[0]);
+ const filePickerInput = this.shadowRoot.getElementById('file-picker');
+ if (filePickerInput) {
+ 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" />
+
+
+
+
+ ${this.editedMessageObj ? (
+ html`
+
+ ${this.isLoading === false ? html`
+
{
+ this.sendMessageFunc();
+ }}
+ >
+
+ ` :
+ html`
+
+ `}
+
+ `
+ ) :
+ html`
+
+ ${this.isLoading === false ? html`
+
{
+ 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