Merge pull request #90 from PhillipLangMartinez/feature/replies-lookup
New chat features
14
.eslintrc.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": ["eslint:recommended", "plugin:lit/recommended", "plugin:wc/recommended"],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 12,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-mixed-spaces-and-tabs": 0
|
||||
}
|
||||
}
|
2
.gitignore
vendored
@ -5,7 +5,7 @@ yarn.lock
|
||||
qortal-ui-plugins/plugins/core/**/*.js
|
||||
!*.src.js
|
||||
qortal-ui-core/src/redux/app/version.js
|
||||
!qortal-ui-plugins/plugins/core/components/*.js
|
||||
!qortal-ui-plugins/plugins/core/components/**/*.js
|
||||
|
||||
# Node modules
|
||||
node_modules/
|
||||
|
BIN
img/badges/level-0.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
img/badges/level-1.png
Normal file
After Width: | Height: | Size: 4.3 KiB |
BIN
img/badges/level-2.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
img/badges/level-3.png
Normal file
After Width: | Height: | Size: 5.5 KiB |
BIN
img/badges/level-4.png
Normal file
After Width: | Height: | Size: 7.4 KiB |
BIN
img/badges/level-5.png
Normal file
After Width: | Height: | Size: 6.6 KiB |
BIN
img/chain.png
Normal file
After Width: | Height: | Size: 174 KiB |
4
img/qchat-send-message-icon.svg
Normal file
@ -0,0 +1,4 @@
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 48 48">
|
||||
<path d="M4.02 42l41.98-18-41.98-18-.02 14 30 4-30 4z" fill="#03a9f4"/>
|
||||
<path d="M0 0h48v48h-48z" fill="none"/>
|
||||
</svg>
|
After Width: | Height: | Size: 253 B |
BIN
img/qortal-chat-logo.png
Normal file
After Width: | Height: | Size: 11 KiB |
15
package.json
@ -19,12 +19,12 @@
|
||||
"install_link:all": "(cd qortal-ui-core && yarn install && yarn link) && (cd qortal-ui-plugins && yarn install && yarn link) && (cd qortal-ui-crypto && yarn install && yarn link) && (yarn link qortal-ui-core && yarn link qortal-ui-plugins && yarn link qortal-ui-crypto)",
|
||||
"dev": "node server.js",
|
||||
"prebuild": "node -p \"'export const UI_VERSION = ' + JSON.stringify(require('./package.json').version) + ';'\" > qortal-ui-core/src/redux/app/version.js",
|
||||
"build-dev": "node build.js",
|
||||
"build": "NODE_ENV=production node build.js",
|
||||
"server": "NODE_ENV=production node server.js",
|
||||
"watch": "node watch.js",
|
||||
"watch-inline": "node watch-inline.js",
|
||||
"start-electron": "NODE_ENV=production electron .",
|
||||
"build-dev": "node --max-old-space-size=8192 build.js",
|
||||
"build": "NODE_ENV=production node --max-old-space-size=8192 build.js",
|
||||
"server": "NODE_ENV=production node --max-old-space-size=8192 server.js",
|
||||
"watch": "node --max-old-space-size=8192 watch.js",
|
||||
"watch-inline": "node --max-old-space-size=8192 watch-inline.js",
|
||||
"start-electron": "NODE_ENV=production electron --js-flags=--max-old-space-size=8192 .",
|
||||
"build-electron": "electron-builder build --publish never",
|
||||
"deploy-electron": "electron-builder build --win --publish never",
|
||||
"release": "NODE_ENV=production electron-builder build --publish never",
|
||||
@ -40,7 +40,8 @@
|
||||
"electron": "22.0.2",
|
||||
"electron-builder": "23.6.0",
|
||||
"electron-packager": "17.1.1",
|
||||
"@electron/notarize": "1.2.3",
|
||||
"eslint-plugin-lit": "1.8.0",
|
||||
"eslint-plugin-wc": "1.4.0",
|
||||
"shelljs": "0.8.5"
|
||||
},
|
||||
"engines": {
|
||||
|
BIN
qortal-ui-core/font/KoHo.ttf
Normal file
BIN
qortal-ui-core/font/Livvic.ttf
Normal file
BIN
qortal-ui-core/font/MaterialSymbolsOutlined.ttf
Normal file
BIN
qortal-ui-core/font/MaterialSymbolsOutlined.woff2
Normal file
BIN
qortal-ui-core/font/Montserrat.ttf
Normal file
BIN
qortal-ui-core/font/Raleway.ttf
Normal file
@ -2,7 +2,8 @@
|
||||
font-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: url(MaterialIcons-Regular.eot); /* For IE6-8 */
|
||||
src: url(MaterialIcons-Regular.eot);
|
||||
/* For IE6-8 */
|
||||
src: local('Material Icons'),
|
||||
local('MaterialIcons-Regular'),
|
||||
url(MaterialIcons-Regular.woff2) format('woff2'),
|
||||
@ -10,11 +11,48 @@
|
||||
url(MaterialIcons-Regular.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Material Symbols Outlined';
|
||||
font-style: normal;
|
||||
src: local('MaterialSymbolsOutlined'),
|
||||
url(MaterialSymbolsOutlined.ttf) format('truetype'),
|
||||
url(MaterialSymbolsOutlined.woff2) format('woff2')
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Montserrat';
|
||||
src: local('Montserrat'),
|
||||
local('Montserrat'),
|
||||
url(Montserrat.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Raleway';
|
||||
src: local('Raleway'),
|
||||
local('Raleway'),
|
||||
url(Raleway.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'KoHo';
|
||||
src: local('KoHo'),
|
||||
local('KoHo'),
|
||||
url(KoHo.ttf) format('truetype');
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Livvic';
|
||||
src: local('Livvic'),
|
||||
local('Livvic'),
|
||||
url(Livvic.ttf) format('truetype');
|
||||
}
|
||||
|
||||
.material-icons {
|
||||
font-family: 'Material Icons';
|
||||
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;
|
||||
@ -34,3 +72,17 @@
|
||||
/* Support for IE. */
|
||||
font-feature-settings: 'liga';
|
||||
}
|
||||
|
||||
.material-symbols-outlined {
|
||||
font-family: 'Material Symbols Outlined';
|
||||
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;
|
||||
}
|
@ -7,6 +7,17 @@ html {
|
||||
--border: #d0d6de;
|
||||
--border2: #dde2e8;
|
||||
--copybutton: #707584;
|
||||
--chat-group: #080808;
|
||||
--chat-bubble: #9f9f9f0a;
|
||||
--chat-bubble-bg: #e6e6e6;
|
||||
--chat-bubble-msg-color: #080808;
|
||||
--reaction-bubble-outline: #6b6969;
|
||||
--chat-menu-bg: #ffffff;
|
||||
--chat-menu-outline: #dad9d9;
|
||||
--chat-menu-icon: #3b3b3c;
|
||||
--chat-menu-icon-hover: #dad9d9;
|
||||
--block-user-bg-hover: #dad9d9;
|
||||
--paperclip-icon: #494949;
|
||||
--sectxt: #576374;
|
||||
--vdicon: #707b8a;
|
||||
--tradehead: #6a6c75;
|
||||
@ -17,6 +28,7 @@ html {
|
||||
--relaynodetxt: #646464;
|
||||
--menuhover: #eeeeee;
|
||||
--menuactive: #ebebeb;
|
||||
--menuactivergb: 235, 235, 235;
|
||||
--mainmenutext: #080808;
|
||||
--mainmenutexthover: #080808;
|
||||
--switchbackground: #666666;
|
||||
@ -32,6 +44,13 @@ html {
|
||||
--nav-border-selected-color: #03a9f4;
|
||||
--error: #d50000;
|
||||
--background: url("/img/qortal_background_light_.jpg");
|
||||
--chatHeadBg: #ebebeb;
|
||||
--chatHeadBgActive: #ebebeb;
|
||||
--chatHeadText: #080808;
|
||||
--chatHeadTextActive: #080808;
|
||||
--lightChatHeadHover: #1e1f201a;
|
||||
--group-header: #929292;
|
||||
--group-drop-shadow: rgb(17 17 26 / 10%) 0px 1px 0px;
|
||||
}
|
||||
|
||||
html[theme="dark"] {
|
||||
@ -43,6 +62,17 @@ html[theme="dark"] {
|
||||
--border: #0b305e;
|
||||
--border2: #0b305e;
|
||||
--copybutton: #d0d6de;
|
||||
--chat-group: #ffffff;
|
||||
--chat-bubble: #9694941a;
|
||||
--chat-bubble-bg: #2d3749;
|
||||
--chat-bubble-msg-color: #ffffff;
|
||||
--reaction-bubble-outline: #ffffff;
|
||||
--chat-menu-bg: #32394c;
|
||||
--chat-menu-outline: #32394c;
|
||||
--chat-menu-icon: #ffffff;
|
||||
--chat-menu-icon-hover: #a49a9a36;
|
||||
--block-user-bg-hover: #121a2f;
|
||||
--paperclip-icon: #d0c9c9;
|
||||
--sectxt: #bbc3cd;
|
||||
--vdicon: #d0d6de;
|
||||
--tradehead: #008fd5;
|
||||
@ -53,6 +83,7 @@ html[theme="dark"] {
|
||||
--relaynodetxt: #d4d4d4;
|
||||
--menuhover: #008fd5;
|
||||
--menuactive: #008fd5;
|
||||
--menuactivergb: 0, 143, 213;
|
||||
--mainmenutext: #008fd5;
|
||||
--mainmenutexthover: #0f1a2e;
|
||||
--switchbackground: #eeeeee;
|
||||
@ -68,4 +99,11 @@ html[theme="dark"] {
|
||||
--nav-border-selected-color: #76c8f5;
|
||||
--error: #d50000;
|
||||
--background: url("/img/qortal_background_dark_.jpg");
|
||||
--chatHeadBg: #008fd5;
|
||||
--chatHeadBgActive: #0f1a2e;
|
||||
--chatHeadText: #ffffff;
|
||||
--chatHeadTextActive: #ffffff;
|
||||
--lightChatHeadHover: #e0e1e31a;
|
||||
--group-header: #c8c8c8;
|
||||
--group-drop-shadow: rgb(191 191 191 / 32%) 0px 1px 0px
|
||||
}
|
@ -513,8 +513,8 @@
|
||||
"cchange3": "Blocked Users",
|
||||
"cchange4": "New Message",
|
||||
"cchange5": "(Click to scroll down)",
|
||||
"cchange6":"Type the name or address of who you want to chat with to send a private message!",
|
||||
"cchange7":"Name / Address",
|
||||
"cchange6": "Type the name or address of who you want to chat with to send a private message! You can validate the person's name by clicking on the book icon.",
|
||||
"cchange7": "Username / Address",
|
||||
"cchange8": "Message...",
|
||||
"cchange9": "Send",
|
||||
"cchange10": "Blocked Users List",
|
||||
@ -526,14 +526,54 @@
|
||||
"cchange16": "Successfully unblocked this user.",
|
||||
"cchange17": "Error occurred when trying to unblock this user. Please try again!",
|
||||
"cchange18": "unblock",
|
||||
"cchange19":"Invalid Name / Address, Check the name / address and retry...",
|
||||
"cchange19": "Invalid Username / Address, Check the name / address and retry...",
|
||||
"cchange20": "Message Sent Successfully!",
|
||||
"cchange21": "Sending failed, Please retry...",
|
||||
"cchange22": "Loading Messages...",
|
||||
"cchange23": "Cannot Decrypt Message!",
|
||||
"cchange24": "Maximum Characters per message is 255",
|
||||
"cchange25":"Your Balance Is Under 4.20 QORT",
|
||||
"cchange26":"Out of the need to combat spam, accounts with under 4.20 Qort balance will take a long time to SEND messages in Q-Chat. If you wish to immediately increase the send speed for Q-Chat messages, obtain over 4.20 QORT to your address. This can be done with trades in the Trade Portal, or by way of another Qortian giving you the QORT. Once you have over 4.20 QORT in your account, Q-Chat messages will be instant and this dialog will no more show. Thank you for your understanding of this necessary spam prevention method, and we hope you enjoy Qortal!"
|
||||
"cchange25": "Edit Message",
|
||||
"cchange26": "File size exceeds 0.5 MB",
|
||||
"cchange27": "A registered name is required to send images",
|
||||
"cchange28": "This file is not an image",
|
||||
"cchange29": "Maximum message size is 1000 bytes",
|
||||
"cchange30": "Uploading image. This may take up to one minute.",
|
||||
"cchange31": "Deleting image. This may take up to one minute.",
|
||||
"cchange33": "Cancel",
|
||||
"cchange34": "This chat message is using an older message version and cannot use this feature.",
|
||||
"cchange35": "Error when trying to fetch the user's name. Please try again!",
|
||||
"cchange36": "Search Results",
|
||||
"cchange37": "No Results Found",
|
||||
"cchange38": "User Verified",
|
||||
"cchange39": "Cannot send an encrypted message to this user since they do not have their publickey on chain.",
|
||||
"cchange40": "IMAGE (click to view)",
|
||||
"cchange41": "Your Balance Is Under 4.20 QORT",
|
||||
"cchange42": "Out of the need to combat spam, accounts with under 4.20 Qort balance will take a long time to SEND messages in Q-Chat. If you wish to immediately increase the send speed for Q-Chat messages, obtain over 4.20 QORT to your address. This can be done with trades in the Trade Portal, or by way of another Qortian giving you the QORT. Once you have over 4.20 QORT in your account, Q-Chat messages will be instant and this dialog will no more show. Thank you for your understanding of this necessary spam prevention method, and we hope you enjoy Qortal!",
|
||||
"cchange43": "Tip QORT to",
|
||||
"cchange44": "SEND MESSAGE",
|
||||
"cchange45": "TIP USER",
|
||||
"cchange46": "Tip Amount",
|
||||
"cchange47": "Available Balance",
|
||||
"cchange48": "Failed to Fetch QORT Balance. Try again!",
|
||||
"cchange49": "Current static fee",
|
||||
"cchange50": "Send",
|
||||
"cchange51": "Insufficient Funds!",
|
||||
"cchange52": "Invalid Amount!",
|
||||
"cchange53": "Receiver cannot be empty!",
|
||||
"cchange54": "Invalid Receiver!",
|
||||
"cchange55": "Transaction Successful!",
|
||||
"cchange56": "Transaction Failed!",
|
||||
"cchange57": "User Info",
|
||||
"cchange58": "SEND MESSAGE",
|
||||
"cchange59": "TIP USER",
|
||||
"cchange60": "Group Invites Pending",
|
||||
"cchange61": "Error when fetching group invites. Please try again!",
|
||||
"cchange62": "Wrong Username and Address Inputted! Please try again!",
|
||||
"cchange63": "Enter Enabled",
|
||||
"cchange64": "Enter Disabled",
|
||||
"cchange65": "Please enter a recipient",
|
||||
"cchange66": "Cannot fetch replied-to message. Message is too old.",
|
||||
"cchange68": "edited"
|
||||
},
|
||||
"welcomepage": {
|
||||
"wcchange1": "Welcome to Q-Chat",
|
||||
@ -556,7 +596,15 @@
|
||||
"bcchange7": "MENU",
|
||||
"bcchange8": "Copy Address",
|
||||
"bcchange9": "Private Message",
|
||||
"bcchange10":"More"
|
||||
"bcchange10": "More",
|
||||
"bcchange11": "Reply",
|
||||
"bcchange12": "Edit",
|
||||
"bcchange13": "Reaction",
|
||||
"bcchange14": "Forward",
|
||||
"bcchange15": "Message Forwarded",
|
||||
"bcchange16": "Choose Recipient or Search for One Below",
|
||||
"bcchange17": "FORWARDED",
|
||||
"bcchange18": "Tip User"
|
||||
},
|
||||
"grouppage": {
|
||||
"gchange1": "Qortal Groups",
|
||||
|
@ -76,7 +76,8 @@
|
||||
"rollup": "3.10.0",
|
||||
"rollup-plugin-node-globals": "1.4.0",
|
||||
"rollup-plugin-progress": "1.1.2",
|
||||
"rollup-plugin-scss": "3.0.0"
|
||||
"rollup-plugin-scss": "3.0.0",
|
||||
"rollup-plugin-web-worker-loader": "1.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.17.1"
|
||||
|
@ -3,6 +3,8 @@ import { connect } from 'pwa-helpers'
|
||||
import { store } from '../store.js'
|
||||
import { doPageUrl } from '../redux/app/app-actions.js'
|
||||
import { translate, translateUnsafeHTML } from 'lit-translate'
|
||||
import WebWorker from 'web-worker:./computePowWorker.js';
|
||||
import { routes } from '../plugins/routes.js';
|
||||
|
||||
import '@material/mwc-icon'
|
||||
import '@material/mwc-button'
|
||||
@ -94,6 +96,8 @@ class AppInfo extends connect(store)(LitElement) {
|
||||
this.nodeStatus = {}
|
||||
this.pageUrl = ''
|
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||
this.publicKeyisOnChainConfirmation = false
|
||||
this.interval
|
||||
}
|
||||
|
||||
render() {
|
||||
@ -108,9 +112,113 @@ class AppInfo extends connect(store)(LitElement) {
|
||||
`
|
||||
}
|
||||
|
||||
async confirmPublicKeyOnChain(address) {
|
||||
const _computePow2 = async (chatBytes) => {
|
||||
const difficulty = 14;
|
||||
const path = window.parent.location.origin + '/memory-pow/memory-pow.wasm.full'
|
||||
const worker = new WebWorker();
|
||||
let nonce = null
|
||||
let chatBytesArray = null
|
||||
await new Promise((res, rej) => {
|
||||
worker.postMessage({chatBytes, path, difficulty});
|
||||
|
||||
worker.onmessage = e => {
|
||||
worker.terminate()
|
||||
chatBytesArray = e.data.chatBytesArray
|
||||
nonce = e.data.nonce
|
||||
res()
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
let _response = await routes.sign_chat({
|
||||
data: {
|
||||
nonce: store.getState().app.selectedAddress.nonce,
|
||||
chatBytesArray: chatBytesArray,
|
||||
chatNonce: nonce
|
||||
},
|
||||
|
||||
});
|
||||
return _response
|
||||
};
|
||||
|
||||
|
||||
let stop = false
|
||||
const checkPublicKey = async () => {
|
||||
if (!stop) {
|
||||
stop = true;
|
||||
try {
|
||||
if(this.publicKeyisOnChainConfirmation){
|
||||
clearInterval(this.interval)
|
||||
return
|
||||
}
|
||||
const myNode = store.getState().app.nodeConfig.knownNodes[store.getState().app.nodeConfig.node];
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
|
||||
const url = `${nodeUrl}/addresses/publickey/${address}`;
|
||||
const res = await fetch(url)
|
||||
let data = ''
|
||||
try {
|
||||
data = await res.text();
|
||||
} catch (error) {
|
||||
data = {
|
||||
error: 'error'
|
||||
}
|
||||
}
|
||||
if(data === 'false' && this.nodeInfo.isSynchronizing !== true){
|
||||
let _reference = new Uint8Array(64);
|
||||
window.crypto.getRandomValues(_reference);
|
||||
let reference = window.parent.Base58.encode(_reference);
|
||||
const chatRes = await routes.chat({
|
||||
data: {
|
||||
type: 19,
|
||||
nonce: store.getState().app.selectedAddress.nonce,
|
||||
params: {
|
||||
lastReference: reference,
|
||||
proofOfWorkNonce: 0,
|
||||
fee: 0,
|
||||
timestamp: Date.now(),
|
||||
|
||||
},
|
||||
disableModal: true
|
||||
},
|
||||
disableModal: true,
|
||||
});
|
||||
|
||||
try {
|
||||
const powRes = await _computePow2(chatRes)
|
||||
if(powRes === true){
|
||||
clearInterval(this.interval)
|
||||
|
||||
this.publicKeyisOnChainConfirmation = true
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
if (!data.error && data !== 'false' && data) {
|
||||
clearInterval(this.interval)
|
||||
|
||||
this.publicKeyisOnChainConfirmation = true
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
}
|
||||
stop = false
|
||||
}
|
||||
};
|
||||
this.interval = setInterval(checkPublicKey, 5000);
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.getNodeInfo()
|
||||
this.getCoreInfo()
|
||||
try {
|
||||
this.confirmPublicKeyOnChain(store.getState().app.selectedAddress.address)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
|
||||
setInterval(() => {
|
||||
this.getNodeInfo()
|
||||
|
@ -142,8 +142,6 @@ class AppView extends connect(store)(LitElement) {
|
||||
|
||||
app-drawer {
|
||||
box-shadow: var(--shadow-2);
|
||||
background: var(--sidetopbar);
|
||||
--app-drawer-scrim-background: rgba(0,0,0,0);
|
||||
}
|
||||
|
||||
app-header {
|
||||
@ -154,6 +152,8 @@ class AppView extends connect(store)(LitElement) {
|
||||
background: var(--sidetopbar);
|
||||
color: var(--black);
|
||||
border-top: var(--border);
|
||||
height: 48px;
|
||||
padding: 3px;
|
||||
}
|
||||
|
||||
paper-progress {
|
||||
@ -188,19 +188,21 @@ class AppView extends connect(store)(LitElement) {
|
||||
flex: 1 1;
|
||||
}
|
||||
|
||||
#sideBar::-webkit-scrollbar {
|
||||
width: 7px;
|
||||
background-color: transparent;
|
||||
.sideBarMenu::-webkit-scrollbar-track {
|
||||
background-color: whitesmoke;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
#sideBar::-webkit-scrollbar-track {
|
||||
background-color: transparent;
|
||||
.sideBarMenu::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
border-radius: 7px;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
#sideBar::-webkit-scrollbar-thumb {
|
||||
background-color: #333;
|
||||
border-radius: 6px;
|
||||
border: 3px solid #333;
|
||||
.sideBarMenu::-webkit-scrollbar-thumb {
|
||||
background-color: rgb(180, 176, 176);
|
||||
border-radius: 7px;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
#balanceheader {
|
||||
@ -323,6 +325,11 @@ class AppView extends connect(store)(LitElement) {
|
||||
0%,100% { opacity: 0; }
|
||||
50% { opacity: 10; }
|
||||
}
|
||||
|
||||
.sideBarMenu::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgb(148, 146, 146);
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
]
|
||||
}
|
||||
|
82
qortal-ui-core/src/components/computePowWorker.js
Normal file
@ -0,0 +1,82 @@
|
||||
import { Sha256 } from 'asmcrypto.js'
|
||||
|
||||
|
||||
function sbrk(size, heap){
|
||||
let brk = 512 * 1024 // stack top
|
||||
let old = brk
|
||||
brk += size
|
||||
|
||||
if (brk > heap.length)
|
||||
throw new Error('heap exhausted')
|
||||
|
||||
return old
|
||||
}
|
||||
|
||||
|
||||
|
||||
self.addEventListener('message', async e => {
|
||||
const response = await computePow(e.data.chatBytes, e.data.path, e.data.difficulty)
|
||||
postMessage(response)
|
||||
|
||||
})
|
||||
|
||||
|
||||
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 })
|
||||
const heap = new Uint8Array(memory.buffer)
|
||||
|
||||
|
||||
|
||||
const computePow = async (chatBytes, path, difficulty) => {
|
||||
|
||||
let response = null
|
||||
|
||||
await new Promise((resolve, reject)=> {
|
||||
|
||||
const _chatBytesArray = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; });
|
||||
const chatBytesArray = new Uint8Array(_chatBytesArray);
|
||||
const chatBytesHash = new Sha256().process(chatBytesArray).finish().result;
|
||||
const hashPtr = sbrk(32, heap);
|
||||
const hashAry = new Uint8Array(memory.buffer, hashPtr, 32);
|
||||
hashAry.set(chatBytesHash);
|
||||
|
||||
|
||||
const workBufferLength = 8 * 1024 * 1024;
|
||||
const workBufferPtr = sbrk(workBufferLength, heap);
|
||||
|
||||
|
||||
|
||||
const importObject = {
|
||||
env: {
|
||||
memory: memory
|
||||
},
|
||||
};
|
||||
|
||||
function loadWebAssembly(filename, imports) {
|
||||
// Fetch the file and compile it
|
||||
return fetch(filename)
|
||||
.then(response => response.arrayBuffer())
|
||||
.then(buffer => WebAssembly.compile(buffer))
|
||||
.then(module => {
|
||||
|
||||
// Create the instance.
|
||||
return new WebAssembly.Instance(module, importObject);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
loadWebAssembly(path)
|
||||
.then(wasmModule => {
|
||||
response = {
|
||||
nonce : wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty),
|
||||
chatBytesArray
|
||||
}
|
||||
|
||||
resolve()
|
||||
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
@ -7,6 +7,17 @@ html {
|
||||
--border: #d0d6de;
|
||||
--border2: #dde2e8;
|
||||
--copybutton: #707584;
|
||||
--chat-group: #080808;
|
||||
--chat-bubble: #9f9f9f0a;
|
||||
--chat-bubble-bg: #e6e6e6;
|
||||
--chat-bubble-msg-color: #080808;
|
||||
--reaction-bubble-outline: #6b6969;
|
||||
--chat-menu-bg: #ffffff;
|
||||
--chat-menu-outline: #dad9d9;
|
||||
--chat-menu-icon: #3b3b3c;
|
||||
--chat-menu-icon-hover: #dad9d9;
|
||||
--block-user-bg-hover: #dad9d9;
|
||||
--paperclip-icon: #494949;
|
||||
--sectxt: #576374;
|
||||
--vdicon: #707b8a;
|
||||
--tradehead: #6a6c75;
|
||||
@ -31,6 +42,12 @@ html {
|
||||
--nav-border-color: #eeeeee;
|
||||
--nav-border-selected-color: #03a9f4;
|
||||
--background: url("/img/qortal_background_light_.jpg");
|
||||
--chatHeadBg: #ebebeb;
|
||||
--chatHeadBgActive: #ebebeb;
|
||||
--chatHeadText: #080808;
|
||||
--chatHeadTextActive: #080808;
|
||||
--group-header: #929292;
|
||||
--group-drop-shadow: rgb(17 17 26 / 10%) 0px 1px 0px;
|
||||
}
|
||||
|
||||
html[theme="dark"] {
|
||||
@ -42,6 +59,17 @@ html[theme="dark"] {
|
||||
--border: #0b305e;
|
||||
--border2: #0b305e;
|
||||
--copybutton: #d0d6de;
|
||||
--chat-group: #ffffff;
|
||||
--chat-bubble: #9694941a;
|
||||
--chat-bubble-bg: #2d3749;
|
||||
--chat-bubble-msg-color: #ffffff;
|
||||
--reaction-bubble-outline: #ffffff;
|
||||
--chat-menu-bg: #32394c;
|
||||
--chat-menu-outline: #32394c;
|
||||
--chat-menu-icon: #ffffff;
|
||||
--chat-menu-icon-hover: #a49a9a36;
|
||||
--block-user-bg-hover: #121a2f;
|
||||
--paperclip-icon: #d0c9c9;
|
||||
--sectxt: #bbc3cd;
|
||||
--vdicon: #d0d6de;
|
||||
--tradehead: #008fd5;
|
||||
@ -66,4 +94,10 @@ html[theme="dark"] {
|
||||
--nav-border-color: #0b305e;
|
||||
--nav-border-selected-color: #76c8f5;
|
||||
--background: url("/img/qortal_background_dark_.jpg");
|
||||
--chatHeadBg: #008fd5;
|
||||
--chatHeadBgActive: #0f1a2e;
|
||||
--chatHeadText: #ffffff;
|
||||
--chatHeadTextActive: #ffffff;
|
||||
--group-header: #c8c8c8;
|
||||
--group-drop-shadow: rgb(191 191 191 / 32%) 0px 1px 0px
|
||||
}
|
@ -7,6 +7,8 @@ const commonjs = require('@rollup/plugin-commonjs')
|
||||
const alias = require('@rollup/plugin-alias')
|
||||
const terser = require('@rollup/plugin-terser');
|
||||
const scss = require('rollup-plugin-scss')
|
||||
const webWorkerLoader = require('rollup-plugin-web-worker-loader');
|
||||
|
||||
const generateES5BuildConfig = require('./generateES5BuildConfig')
|
||||
|
||||
|
||||
@ -61,6 +63,7 @@ const generateBuildConfig = ({ elementComponents, functionalComponents, otherOut
|
||||
commonjs(),
|
||||
globals(),
|
||||
progress(),
|
||||
webWorkerLoader(),
|
||||
scss({
|
||||
output: options.sassOutputDir
|
||||
}),
|
||||
|
@ -5,6 +5,7 @@ const commonjs = require('@rollup/plugin-commonjs');
|
||||
const progress = require('rollup-plugin-progress');
|
||||
const terser = require('@rollup/plugin-terser');
|
||||
const alias = require('@rollup/plugin-alias');
|
||||
const webWorkerLoader = require('rollup-plugin-web-worker-loader');
|
||||
const path = require('path');
|
||||
|
||||
|
||||
@ -37,6 +38,7 @@ const generateRollupConfig = (file, { outputDir, aliases }) => {
|
||||
}),
|
||||
commonjs(),
|
||||
progress(),
|
||||
webWorkerLoader(),
|
||||
babel.babel({
|
||||
babelHelpers: 'bundled',
|
||||
exclude: 'node_modules/**'
|
||||
|
@ -1,5 +1,6 @@
|
||||
'use strict'
|
||||
import ChatBase from './chat/ChatBase.js'
|
||||
"use strict";
|
||||
import ChatBase from "./chat/ChatBase.js"
|
||||
import { QORT_DECIMALS } from "../constants.js"
|
||||
|
||||
export default class PublicizeTransaction extends ChatBase {
|
||||
constructor() {
|
||||
@ -11,13 +12,16 @@ export default class PublicizeTransaction extends ChatBase {
|
||||
set proofOfWorkNonce(proofOfWorkNonce) {
|
||||
this._proofOfWorkNonce = this.constructor.utils.int32ToBytes(proofOfWorkNonce)
|
||||
}
|
||||
|
||||
set fee(fee) {
|
||||
this._fee = fee * QORT_DECIMALS
|
||||
this._feeBytes = this.constructor.utils.int64ToBytes(this._fee)
|
||||
}
|
||||
get params() {
|
||||
const params = super.params
|
||||
const params = super.params;
|
||||
params.push(
|
||||
this._proofOfWorkNonce,
|
||||
this._feeBytes
|
||||
)
|
||||
return params
|
||||
return params;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,72 @@
|
||||
'use strict';
|
||||
import TransactionBase from '../TransactionBase.js'
|
||||
import Base58 from '../../deps/Base58.js'
|
||||
import { store } from '../../../api.js'
|
||||
import { QORT_DECIMALS } from "../../constants.js"
|
||||
|
||||
export default class UpdateGroupTransaction extends TransactionBase {
|
||||
constructor() {
|
||||
super()
|
||||
this.type = 23
|
||||
}
|
||||
|
||||
render(html) {
|
||||
const conf = store.getState().config
|
||||
return html`
|
||||
Are you sure to update this group ?
|
||||
<div style="background: #eee; padding: 8px; margin: 8px 0; border-radius: 5px;">
|
||||
|
||||
</div>
|
||||
On pressing confirm, the group details will be updated!
|
||||
`
|
||||
}
|
||||
|
||||
|
||||
set fee(fee) {
|
||||
this._fee = fee * QORT_DECIMALS
|
||||
this._feeBytes = this.constructor.utils.int64ToBytes(this._fee)
|
||||
}
|
||||
set newOwner(newOwner) {
|
||||
this._newOwner = newOwner instanceof Uint8Array ? newOwner : this.constructor.Base58.decode(newOwner)
|
||||
}
|
||||
set newIsOpen(newIsOpen) {
|
||||
|
||||
this._rGroupType = new Uint8Array(1)
|
||||
this._rGroupType[0] = newIsOpen
|
||||
}
|
||||
set newDescription(newDescription) {
|
||||
this._rGroupDescBytes = this.constructor.utils.stringtoUTF8Array(newDescription.toLocaleLowerCase())
|
||||
this._rGroupDescLength = this.constructor.utils.int32ToBytes(this._rGroupDescBytes.length)
|
||||
}
|
||||
set newApprovalThreshold(newApprovalThreshold) {
|
||||
this._rGroupApprovalThreshold = new Uint8Array(1)
|
||||
this._rGroupApprovalThreshold[0] = newApprovalThreshold;
|
||||
}
|
||||
set newMinimumBlockDelay(newMinimumBlockDelay) {
|
||||
this._rGroupMinimumBlockDelayBytes = this.constructor.utils.int32ToBytes(newMinimumBlockDelay)
|
||||
}
|
||||
set newMaximumBlockDelay(newMaximumBlockDelay) {
|
||||
|
||||
this._rGroupMaximumBlockDelayBytes = this.constructor.utils.int32ToBytes(newMaximumBlockDelay)
|
||||
}
|
||||
|
||||
set _groupId(_groupId){
|
||||
this._groupBytes = this.constructor.utils.int32ToBytes(_groupId)
|
||||
}
|
||||
get params() {
|
||||
const params = super.params
|
||||
params.push(
|
||||
this._groupBytes,
|
||||
this._newOwner,
|
||||
this._rGroupDescLength,
|
||||
this._rGroupDescBytes,
|
||||
this._rGroupType,
|
||||
this._rGroupApprovalThreshold,
|
||||
this._rGroupMinimumBlockDelayBytes,
|
||||
this._rGroupMaximumBlockDelayBytes,
|
||||
this._feeBytes
|
||||
)
|
||||
console.log('verify params', params)
|
||||
return params
|
||||
}
|
||||
}
|
@ -16,6 +16,7 @@ import GroupKickTransaction from './groups/GroupKickTransaction.js'
|
||||
import GroupInviteTransaction from './groups/GroupInviteTransaction.js'
|
||||
import CancelGroupInviteTransaction from './groups/CancelGroupInviteTransaction.js'
|
||||
import JoinGroupTransaction from './groups/JoinGroupTransaction.js'
|
||||
import UpdateGroupTransaction from './groups/UpdateGroupTransaction.js'
|
||||
import LeaveGroupTransaction from './groups/LeaveGroupTransaction.js'
|
||||
import RewardShareTransaction from './reward-share/RewardShareTransaction.js'
|
||||
import RemoveRewardShareTransaction from './reward-share/RemoveRewardShareTransaction.js'
|
||||
|
@ -8,6 +8,8 @@ const commonjs = require('@rollup/plugin-commonjs');
|
||||
const alias = require('@rollup/plugin-alias');
|
||||
const terser = require('@rollup/plugin-terser');
|
||||
const babel = require('@rollup/plugin-babel');
|
||||
const webWorkerLoader = require('rollup-plugin-web-worker-loader');
|
||||
|
||||
|
||||
const aliases = {};
|
||||
|
||||
@ -40,6 +42,7 @@ const generateRollupConfig = (inputFile, outputFile) => {
|
||||
commonjs(),
|
||||
globals(),
|
||||
progress(),
|
||||
webWorkerLoader(),
|
||||
babel.babel({
|
||||
babelHelpers: 'bundled',
|
||||
exclude: 'node_modules/**',
|
||||
|
@ -17,9 +17,31 @@
|
||||
"author": "QORTAL <admin@qortal.org>",
|
||||
"license": "GPL-3.0",
|
||||
"dependencies": {
|
||||
"@lit-labs/motion": "1.0.3",
|
||||
"@material/mwc-list": "0.27.0",
|
||||
"@material/mwc-select": "0.27.0",
|
||||
"emoji-picker-js": "https://github.com/Qortal/emoji-picker-js"
|
||||
"@tiptap/core": "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",
|
||||
"compressorjs": "1.1.1",
|
||||
"emoji-picker-js": "https://github.com/Qortal/emoji-picker-js",
|
||||
"prosemirror-commands": "1.5.0",
|
||||
"prosemirror-dropcursor": "1.6.1",
|
||||
"prosemirror-gapcursor": "1.3.1",
|
||||
"prosemirror-history": "1.3.0",
|
||||
"prosemirror-keymap": "1.2.0",
|
||||
"prosemirror-model": "1.18.3",
|
||||
"prosemirror-schema-list": "1.2.2",
|
||||
"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": {
|
||||
"@babel/core": "7.20.12",
|
||||
@ -60,7 +82,8 @@
|
||||
"lit-translate": "2.0.1",
|
||||
"rollup": "3.10.0",
|
||||
"rollup-plugin-node-globals": "1.4.0",
|
||||
"rollup-plugin-progress": "1.1.2"
|
||||
"rollup-plugin-progress": "1.1.2",
|
||||
"rollup-plugin-web-worker-loader": "1.6.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.17.1"
|
||||
|
335
qortal-ui-plugins/plugins/core/components/ChatGroupInvites.js
Normal file
@ -0,0 +1,335 @@
|
||||
import { LitElement, html, css } from "lit"
|
||||
import { render } from "lit/html.js"
|
||||
import { get, translate } from "lit-translate"
|
||||
import { Epml } from "../../../epml"
|
||||
import snackbar from "./snackbar.js"
|
||||
import "@material/mwc-button"
|
||||
import "@material/mwc-dialog"
|
||||
import "@polymer/paper-spinner/paper-spinner-lite.js"
|
||||
import "@material/mwc-icon"
|
||||
import "./WrapperModal"
|
||||
|
||||
const parentEpml = new Epml({ type: "WINDOW", source: window.parent })
|
||||
|
||||
class ChatGroupInvites extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
isLoading: { type: Boolean },
|
||||
isOpenLeaveModal: { type: Boolean },
|
||||
leaveGroupObj: { type: Object },
|
||||
error: { type: Boolean },
|
||||
message: { type: String },
|
||||
chatHeads: { type: Array },
|
||||
groupAdmin: { attribute: false },
|
||||
groupMembers: { attribute: false },
|
||||
selectedHead: { type: Object },
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.isLoading = false
|
||||
this.isOpenLeaveModal = false
|
||||
this.leaveGroupObj = {}
|
||||
this.leaveFee = 0.001
|
||||
this.error = false
|
||||
this.message = ""
|
||||
this.chatHeads = []
|
||||
this.groupAdmin = []
|
||||
this.groupMembers = []
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
.top-bar-icon {
|
||||
cursor: pointer;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
transition: 0.2s all;
|
||||
}
|
||||
.top-bar-icon:hover {
|
||||
color: var(--black);
|
||||
}
|
||||
.modal-button {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 16px;
|
||||
color: var(--mdc-theme-primary);
|
||||
background-color: transparent;
|
||||
padding: 8px 10px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {}
|
||||
|
||||
timeIsoString(timestamp) {
|
||||
let myTimestamp = timestamp === undefined ? 1587560082346 : timestamp
|
||||
let time = new Date(myTimestamp)
|
||||
return time.toISOString()
|
||||
}
|
||||
|
||||
resetDefaultSettings() {
|
||||
this.error = false
|
||||
this.message = ""
|
||||
this.isLoading = false
|
||||
}
|
||||
|
||||
renderErr9Text() {
|
||||
return html`${translate("grouppage.gchange49")}`
|
||||
}
|
||||
|
||||
async confirmRelationship(reference) {
|
||||
let interval = null
|
||||
let stop = false
|
||||
const getAnswer = async () => {
|
||||
|
||||
|
||||
if (!stop) {
|
||||
stop = true
|
||||
try {
|
||||
let myRef = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
url: `/transactions/reference/${reference}`,
|
||||
})
|
||||
if (myRef && myRef.type) {
|
||||
clearInterval(interval)
|
||||
this.isLoading = false
|
||||
this.isOpenLeaveModal = false
|
||||
}
|
||||
} catch (error) {}
|
||||
stop = false
|
||||
}
|
||||
}
|
||||
interval = setInterval(getAnswer, 5000)
|
||||
}
|
||||
|
||||
async getLastRef() {
|
||||
let myRef = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
url: `/addresses/lastreference/${this.selectedAddress.address}`,
|
||||
})
|
||||
return myRef
|
||||
}
|
||||
|
||||
getTxnRequestResponse(txnResponse, reference) {
|
||||
if (txnResponse === true) {
|
||||
this.message = this.renderErr9Text()
|
||||
this.error = false
|
||||
this.confirmRelationship(reference)
|
||||
} else {
|
||||
this.error = true
|
||||
this.message = ""
|
||||
throw new Error(txnResponse)
|
||||
}
|
||||
}
|
||||
|
||||
async convertBytesForSigning(transactionBytesBase58) {
|
||||
let convertedBytes = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
method: "POST",
|
||||
url: `/transactions/convert`,
|
||||
body: `${transactionBytesBase58}`,
|
||||
})
|
||||
return convertedBytes
|
||||
}
|
||||
|
||||
async signTx(body){
|
||||
return await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
method: "POST",
|
||||
url: `/transactions/sign`,
|
||||
body: body,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async process(body){
|
||||
return await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
method: "POST",
|
||||
url: `/transactions/process`,
|
||||
body: body,
|
||||
})
|
||||
}
|
||||
async _addAdmin(groupId) {
|
||||
// Reset Default Settings...
|
||||
this.resetDefaultSettings()
|
||||
const leaveFeeInput = this.leaveFee
|
||||
|
||||
this.isLoading = true
|
||||
|
||||
// Get Last Ref
|
||||
|
||||
const validateReceiver = async () => {
|
||||
let lastRef = await this.getLastRef()
|
||||
let myTransaction = await makeTransactionRequest(lastRef)
|
||||
this.getTxnRequestResponse(myTransaction, lastRef )
|
||||
}
|
||||
|
||||
// Make Transaction Request
|
||||
const makeTransactionRequest = async (lastRef) => {
|
||||
const body = {
|
||||
timestamp: Date.now(),
|
||||
reference: lastRef,
|
||||
fee: leaveFeeInput,
|
||||
ownerPublicKey: window.parent.Base58.encode(
|
||||
window.parent.reduxStore.getState().app.selectedAddress
|
||||
.keyPair.publicKey
|
||||
),
|
||||
groupId: groupId,
|
||||
member: this.selectedHead.address,
|
||||
}
|
||||
const bodyToString = JSON.stringify(body)
|
||||
let transactionBytes = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
method: "POST",
|
||||
url: `/groups/addadmin`,
|
||||
body: bodyToString,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
const readforsign = await this.convertBytesForSigning(
|
||||
transactionBytes
|
||||
)
|
||||
const body2 = {
|
||||
privateKey: window.parent.Base58.encode(
|
||||
window.parent.reduxStore.getState().app.selectedAddress
|
||||
.keyPair.privateKey
|
||||
),
|
||||
transactionBytes: readforsign,
|
||||
}
|
||||
const bodyToString2 = JSON.stringify(body2)
|
||||
let signTransaction = await this.signTx(bodyToString2)
|
||||
let processTransaction = await this.process(signTransaction)
|
||||
return processTransaction
|
||||
}
|
||||
|
||||
validateReceiver()
|
||||
}
|
||||
|
||||
async _removeAdmin(groupId) {
|
||||
// Reset Default Settings...
|
||||
this.resetDefaultSettings()
|
||||
const leaveFeeInput = this.leaveFee
|
||||
|
||||
this.isLoading = true
|
||||
|
||||
// Get Last Ref
|
||||
|
||||
const validateReceiver = async () => {
|
||||
let lastRef = await this.getLastRef()
|
||||
let myTransaction = await makeTransactionRequest(lastRef)
|
||||
this.getTxnRequestResponse(myTransaction, lastRef)
|
||||
}
|
||||
|
||||
// Make Transaction Request
|
||||
const makeTransactionRequest = async (lastRef) => {
|
||||
const body = {
|
||||
timestamp: Date.now(),
|
||||
reference: lastRef,
|
||||
fee: leaveFeeInput,
|
||||
ownerPublicKey: window.parent.Base58.encode(
|
||||
window.parent.reduxStore.getState().app.selectedAddress
|
||||
.keyPair.publicKey
|
||||
),
|
||||
groupId: groupId,
|
||||
admin: this.selectedHead.address,
|
||||
}
|
||||
const bodyToString = JSON.stringify(body)
|
||||
let transactionBytes = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
method: "POST",
|
||||
url: `/groups/removeadmin`,
|
||||
body: bodyToString,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
})
|
||||
const readforsign = await this.convertBytesForSigning(
|
||||
transactionBytes
|
||||
)
|
||||
const body2 = {
|
||||
privateKey: window.parent.Base58.encode(
|
||||
window.parent.reduxStore.getState().app.selectedAddress
|
||||
.keyPair.privateKey
|
||||
),
|
||||
transactionBytes: readforsign,
|
||||
}
|
||||
const bodyToString2 = JSON.stringify(body2)
|
||||
let signTransaction = await this.signTx(bodyToString2)
|
||||
let processTransaction = await this.process(signTransaction)
|
||||
return processTransaction
|
||||
}
|
||||
|
||||
validateReceiver()
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log("leaveGroupObj", this.leaveGroupObj)
|
||||
return html`
|
||||
<vaadin-icon @click=${()=> {
|
||||
this.isOpenLeaveModal = true
|
||||
}} class="top-bar-icon" style="margin: 0px 20px" icon="vaadin:users" slot="icon"></vaadin-icon>
|
||||
|
||||
<wrapper-modal
|
||||
.removeImage=${() => {
|
||||
if (this.isLoading) return
|
||||
this.isOpenLeaveModal = false
|
||||
}}
|
||||
style=${
|
||||
this.isOpenLeaveModal ? "display: block" : "display: none"
|
||||
}>
|
||||
<div style="text-align:center">
|
||||
<h1>${translate("grouppage.gchange35")}</h1>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<button @click=${() =>
|
||||
this._addAdmin(
|
||||
this.leaveGroupObj.groupId
|
||||
)}>Promote to Admin</button>
|
||||
<button @click=${() =>
|
||||
this._removeAdmin(
|
||||
this.leaveGroupObj.groupId
|
||||
)}>Remove as Admin</button>
|
||||
<div style="text-align:right; height:36px;">
|
||||
<span ?hidden="${!this.isLoading}">
|
||||
<!-- loading message -->
|
||||
${translate("grouppage.gchange36")}
|
||||
<paper-spinner-lite
|
||||
style="margin-top:12px;"
|
||||
?active="${this.isLoading}"
|
||||
alt="Leaving"
|
||||
>
|
||||
</paper-spinner-lite>
|
||||
</span>
|
||||
<span ?hidden=${this.message === ""} style="${
|
||||
this.error ? "color:red;" : ""
|
||||
}">
|
||||
${this.message}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<button
|
||||
@click=${() => {
|
||||
this.isOpenLeaveModal = false
|
||||
}}
|
||||
class="modal-button"
|
||||
?disabled="${this.isLoading}"
|
||||
|
||||
>
|
||||
${translate("general.close")}
|
||||
</button>
|
||||
</wrapper-modal >
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("chat-right-panel", ChatGroupInvites)
|
283
qortal-ui-plugins/plugins/core/components/ChatGroupSettings.js
Normal file
@ -0,0 +1,283 @@
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { render } from 'lit/html.js';
|
||||
import { get, translate } from 'lit-translate';
|
||||
import { Epml } from '../../../epml';
|
||||
import snackbar from './snackbar.js'
|
||||
import '@material/mwc-button';
|
||||
import '@material/mwc-dialog';
|
||||
import '@polymer/paper-spinner/paper-spinner-lite.js'
|
||||
import '@material/mwc-icon';
|
||||
import './WrapperModal';
|
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
||||
|
||||
class ChatGroupSettings extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
isLoading: { type: Boolean },
|
||||
isOpenLeaveModal: {type: Boolean},
|
||||
leaveGroupObj: { type: Object },
|
||||
error: {type: Boolean},
|
||||
message: {type: String},
|
||||
chatHeads: {type: Array},
|
||||
setActiveChatHeadUrl: {attribute: false}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.isLoading = false;
|
||||
this.isOpenLeaveModal = false
|
||||
this.leaveGroupObj = {}
|
||||
this.leaveFee = 0.001
|
||||
this.error = false
|
||||
this.message = ''
|
||||
this.chatHeads = []
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
.top-bar-icon {
|
||||
cursor: pointer;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
transition: .2s all;
|
||||
}
|
||||
|
||||
.top-bar-icon:hover {
|
||||
color: var(--black)
|
||||
}
|
||||
|
||||
.modal-button {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 16px;
|
||||
color: var(--mdc-theme-primary);
|
||||
background-color: transparent;
|
||||
padding: 8px 10px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
|
||||
}
|
||||
|
||||
timeIsoString(timestamp) {
|
||||
let myTimestamp = timestamp === undefined ? 1587560082346 : timestamp
|
||||
let time = new Date(myTimestamp)
|
||||
return time.toISOString()
|
||||
}
|
||||
|
||||
resetDefaultSettings() {
|
||||
this.error = false
|
||||
this.message = ''
|
||||
this.isLoading = false
|
||||
}
|
||||
|
||||
renderErr9Text() {
|
||||
return html`${translate("grouppage.gchange49")}`
|
||||
}
|
||||
|
||||
async confirmRelationship() {
|
||||
|
||||
|
||||
let interval = null
|
||||
let stop = false
|
||||
const getAnswer = async () => {
|
||||
const currentChats = this.chatHeads
|
||||
|
||||
if (!stop) {
|
||||
stop = true;
|
||||
try {
|
||||
const findGroup = currentChats.find((item)=> item.groupId === this.leaveGroupObj.groupId)
|
||||
if (!findGroup) {
|
||||
clearInterval(interval)
|
||||
this.isLoading = false
|
||||
this.isOpenLeaveModal= false
|
||||
this.setActiveChatHeadUrl('')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
}
|
||||
stop = false
|
||||
}
|
||||
};
|
||||
interval = setInterval(getAnswer, 5000);
|
||||
}
|
||||
|
||||
async _convertToPrivate(groupId) {
|
||||
// Reset Default Settings...
|
||||
this.resetDefaultSettings()
|
||||
const leaveFeeInput = this.leaveFee
|
||||
|
||||
this.isLoading = true
|
||||
|
||||
// Get Last Ref
|
||||
const getLastRef = async () => {
|
||||
let myRef = await parentEpml.request('apiCall', {
|
||||
type: 'api',
|
||||
url: `/addresses/lastreference/${this.selectedAddress.address}`
|
||||
})
|
||||
return myRef
|
||||
};
|
||||
|
||||
const validateReceiver = async () => {
|
||||
let lastRef = await getLastRef();
|
||||
let myTransaction = await makeTransactionRequest(lastRef)
|
||||
getTxnRequestResponse(myTransaction)
|
||||
|
||||
}
|
||||
const convertBytesForSigning = async (transactionBytesBase58) => {
|
||||
let convertedBytes = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
method: "POST",
|
||||
url: `/transactions/convert`,
|
||||
body: `${transactionBytesBase58}`,
|
||||
})
|
||||
return convertedBytes
|
||||
}
|
||||
|
||||
|
||||
// Make Transaction Request
|
||||
const makeTransactionRequest = async (lastRef) => {
|
||||
let groupdialog3 = get("transactions.groupdialog3")
|
||||
let groupdialog4 = get("transactions.groupdialog4")
|
||||
|
||||
const body = {
|
||||
"timestamp": Date.now(),
|
||||
"reference": lastRef,
|
||||
"fee": leaveFeeInput,
|
||||
"ownerPublicKey": window.parent.Base58.encode(window.parent.reduxStore.getState().app.selectedAddress.keyPair.publicKey),
|
||||
"groupId": groupId,
|
||||
"newOwner": "QdR4bQ1fJFnSZgswtW27eE8ToXwHqUQyaU",
|
||||
"newIsOpen": false,
|
||||
"newDescription": "my group for accounts I like",
|
||||
"newApprovalThreshold": "NONE",
|
||||
"newMinimumBlockDelay": 5,
|
||||
"newMaximumBlockDelay": 60
|
||||
}
|
||||
console.log('STRING3')
|
||||
// const bodyToString = JSON.stringify(body)
|
||||
// let transactionBytes = await parentEpml.request("apiCall", {
|
||||
// type: "api",
|
||||
// method: "POST",
|
||||
// url: `/groups/update`,
|
||||
// body: bodyToString,
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json'
|
||||
// }
|
||||
// })
|
||||
// console.log({transactionBytes})
|
||||
// const readforsign = await convertBytesForSigning(transactionBytes)
|
||||
// // const res = await signAndProcess(transactionBytes)
|
||||
// const body2 = {
|
||||
// "privateKey": window.parent.Base58.encode(window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey),
|
||||
// "transactionBytes": readforsign
|
||||
// }
|
||||
// const bodyToString2 = JSON.stringify(body2)
|
||||
// let signTransaction = await parentEpml.request("apiCall", {
|
||||
// type: "api",
|
||||
// method: "POST",
|
||||
// url: `/transactions/sign`,
|
||||
// body: bodyToString2,
|
||||
// headers: {
|
||||
// 'Content-Type': 'application/json'
|
||||
// }
|
||||
// })
|
||||
// let processTransaction = await parentEpml.request("apiCall", {
|
||||
// type: "api",
|
||||
// method: "POST",
|
||||
// url: `/transactions/process`,
|
||||
// body: signTransaction,
|
||||
// })
|
||||
// return processTransaction
|
||||
console.log('this.selectedAddress.nonce', this.selectedAddress.nonce)
|
||||
let myTxnrequest = await parentEpml.request('transaction', {
|
||||
type: 23,
|
||||
nonce: this.selectedAddress.nonce,
|
||||
params: {
|
||||
_groupId: groupId,
|
||||
lastReference: lastRef,
|
||||
fee: leaveFeeInput,
|
||||
"newOwner": "QdR4bQ1fJFnSZgswtW27eE8ToXwHqUQyaU",
|
||||
"newIsOpen": false,
|
||||
"newDescription": "my group for accounts I like",
|
||||
"newApprovalThreshold": "NONE",
|
||||
"newMinimumBlockDelay": 5,
|
||||
"newMaximumBlockDelay": 60
|
||||
}
|
||||
})
|
||||
return myTxnrequest
|
||||
}
|
||||
|
||||
const getTxnRequestResponse = (txnResponse) => {
|
||||
|
||||
if (txnResponse === true) {
|
||||
this.message = this.renderErr9Text()
|
||||
this.error = false
|
||||
this.confirmRelationship()
|
||||
} else {
|
||||
this.error = true
|
||||
this.message = ""
|
||||
throw new Error(txnResponse)
|
||||
}
|
||||
}
|
||||
validateReceiver()
|
||||
}
|
||||
|
||||
render() {
|
||||
console.log('leaveGroupObj', this.leaveGroupObj)
|
||||
return html`
|
||||
<vaadin-icon @click=${()=> {
|
||||
this.isOpenLeaveModal = true
|
||||
}} class="top-bar-icon" style="margin: 0px 20px" icon="vaadin:cog" slot="icon"></vaadin-icon>
|
||||
<!-- Leave Group Dialog -->
|
||||
<wrapper-modal
|
||||
.removeImage=${() => {
|
||||
if(this.isLoading) return
|
||||
this.isOpenLeaveModal = false
|
||||
} }
|
||||
style=${(this.isOpenLeaveModal) ? "display: block" : "display: none"}>
|
||||
<div style="text-align:center">
|
||||
<h1>${translate("grouppage.gchange35")}</h1>
|
||||
<hr>
|
||||
</div>
|
||||
<
|
||||
<button @click=${() => this._convertToPrivate(this.leaveGroupObj.groupId, this.leaveGroupObj.groupName)}> Convert a public group to private</button>
|
||||
|
||||
<div style="text-align:right; height:36px;">
|
||||
<span ?hidden="${!this.isLoading}">
|
||||
<!-- loading message -->
|
||||
${translate("grouppage.gchange36")}
|
||||
<paper-spinner-lite
|
||||
style="margin-top:12px;"
|
||||
?active="${this.isLoading}"
|
||||
alt="Leaving"
|
||||
>
|
||||
</paper-spinner-lite>
|
||||
</span>
|
||||
<span ?hidden=${this.message === ''} style="${this.error ? 'color:red;' : ''}">
|
||||
${this.message}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
|
||||
<button
|
||||
@click=${() => {
|
||||
this.isOpenLeaveModal= false
|
||||
}}
|
||||
class="modal-button"
|
||||
?disabled="${this.isLoading}"
|
||||
|
||||
>
|
||||
${translate("general.close")}
|
||||
</button>
|
||||
</wrapper-modal >
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('chat-group-settings', ChatGroupSettings);
|
@ -0,0 +1,296 @@
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { render } from 'lit/html.js';
|
||||
import { get, translate } from 'lit-translate';
|
||||
import { Epml } from '../../../epml';
|
||||
import snackbar from './snackbar.js'
|
||||
import '@material/mwc-button';
|
||||
import '@material/mwc-dialog';
|
||||
import '@polymer/paper-spinner/paper-spinner-lite.js'
|
||||
import '@material/mwc-icon';
|
||||
import './WrapperModal';
|
||||
import '@vaadin/tabs'
|
||||
import '@vaadin/tabs/theme/material/vaadin-tabs.js';
|
||||
import '@vaadin/avatar';
|
||||
import '@vaadin/grid';
|
||||
import '@vaadin/grid/vaadin-grid-filter-column.js';
|
||||
import { columnBodyRenderer } from '@vaadin/grid/lit.js';
|
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
||||
|
||||
class ChatGroupsManagement extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
isLoading: { type: Boolean },
|
||||
isOpenLeaveModal: {type: Boolean},
|
||||
leaveGroupObj: { type: Object },
|
||||
error: {type: Boolean},
|
||||
message: {type: String},
|
||||
chatHeads: {type: Array},
|
||||
setActiveChatHeadUrl: {attribute: false},
|
||||
selectedAddress: {attribute: Object},
|
||||
currentTab: {type: Number},
|
||||
groups: {type: Array}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.isLoading = false;
|
||||
this.isOpenLeaveModal = false
|
||||
this.leaveGroupObj = {}
|
||||
this.fee = null
|
||||
this.error = false
|
||||
this.message = ''
|
||||
this.chatHeads = []
|
||||
this.currentTab = 0
|
||||
this.groups = []
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
.top-bar-icon {
|
||||
cursor: pointer;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
transition: .2s all;
|
||||
}
|
||||
.top-bar-icon:hover {
|
||||
color: var(--black)
|
||||
}
|
||||
.modal-button {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 16px;
|
||||
color: var(--mdc-theme-primary);
|
||||
background-color: transparent;
|
||||
padding: 8px 10px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
async getJoinedGroups(){
|
||||
let joinedG = await parentEpml.request('apiCall', {
|
||||
url: `/groups/member/${this.selectedAddress.address}`
|
||||
})
|
||||
return joinedG
|
||||
}
|
||||
|
||||
async firstUpdated() {
|
||||
|
||||
try {
|
||||
let _joinedGroups = await this.getJoinedGroups()
|
||||
this.joinedGroups = _joinedGroups
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
_tabChanged(e) {
|
||||
this.currentTab = e.detail.value
|
||||
}
|
||||
|
||||
async unitFee() {
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
const url = `${nodeUrl}/transactions/unitfee?txType=LEAVE_GROUP`
|
||||
let fee = null
|
||||
|
||||
try {
|
||||
const res = await fetch(url)
|
||||
const data = await res.json()
|
||||
fee = (Number(data) / 1e8).toFixed(3)
|
||||
} catch (error) {
|
||||
fee = null
|
||||
}
|
||||
|
||||
return fee
|
||||
}
|
||||
|
||||
timeIsoString(timestamp) {
|
||||
let myTimestamp = timestamp === undefined ? 1587560082346 : timestamp
|
||||
let time = new Date(myTimestamp)
|
||||
return time.toISOString()
|
||||
}
|
||||
|
||||
resetDefaultSettings() {
|
||||
this.error = false
|
||||
this.message = ''
|
||||
this.isLoading = false
|
||||
}
|
||||
|
||||
renderErr9Text() {
|
||||
return html`${translate("grouppage.gchange49")}`
|
||||
}
|
||||
|
||||
async confirmRelationship() {
|
||||
|
||||
|
||||
let interval = null
|
||||
let stop = false
|
||||
const getAnswer = async () => {
|
||||
const currentChats = this.chatHeads
|
||||
|
||||
if (!stop) {
|
||||
stop = true;
|
||||
try {
|
||||
const findGroup = currentChats.find((item)=> item.groupId === this.leaveGroupObj.groupId)
|
||||
if (!findGroup) {
|
||||
clearInterval(interval)
|
||||
this.isLoading = false
|
||||
this.isOpenLeaveModal= false
|
||||
this.setActiveChatHeadUrl('')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
}
|
||||
stop = false
|
||||
}
|
||||
};
|
||||
interval = setInterval(getAnswer, 5000);
|
||||
}
|
||||
|
||||
async _leaveGroup(groupId, groupName) {
|
||||
// Reset Default Settings...
|
||||
this.resetDefaultSettings()
|
||||
|
||||
const leaveFeeInput = await this.unitFee()
|
||||
if(!leaveFeeInput){
|
||||
throw Error()
|
||||
}
|
||||
this.isLoading = true
|
||||
|
||||
// Get Last Ref
|
||||
const getLastRef = async () => {
|
||||
let myRef = await parentEpml.request('apiCall', {
|
||||
type: 'api',
|
||||
url: `/addresses/lastreference/${this.selectedAddress.address}`
|
||||
})
|
||||
return myRef
|
||||
};
|
||||
|
||||
const validateReceiver = async () => {
|
||||
let lastRef = await getLastRef();
|
||||
let myTransaction = await makeTransactionRequest(lastRef)
|
||||
getTxnRequestResponse(myTransaction)
|
||||
|
||||
}
|
||||
|
||||
// Make Transaction Request
|
||||
const makeTransactionRequest = async (lastRef) => {
|
||||
let groupdialog3 = get("transactions.groupdialog3")
|
||||
let groupdialog4 = get("transactions.groupdialog4")
|
||||
let myTxnrequest = await parentEpml.request('transaction', {
|
||||
type: 32,
|
||||
nonce: this.selectedAddress.nonce,
|
||||
params: {
|
||||
fee: leaveFeeInput,
|
||||
registrantAddress: this.selectedAddress.address,
|
||||
rGroupName: groupName,
|
||||
rGroupId: groupId,
|
||||
lastReference: lastRef,
|
||||
groupdialog3: groupdialog3,
|
||||
groupdialog4: groupdialog4,
|
||||
}
|
||||
})
|
||||
return myTxnrequest
|
||||
}
|
||||
|
||||
const getTxnRequestResponse = (txnResponse) => {
|
||||
|
||||
if (txnResponse.success === false && txnResponse.message) {
|
||||
this.error = true
|
||||
this.message = txnResponse.message
|
||||
throw new Error(txnResponse)
|
||||
} else if (txnResponse.success === true && !txnResponse.data.error) {
|
||||
this.message = this.renderErr9Text()
|
||||
this.error = false
|
||||
this.confirmRelationship()
|
||||
} else {
|
||||
this.error = true
|
||||
this.message = txnResponse.data.message
|
||||
throw new Error(txnResponse)
|
||||
}
|
||||
}
|
||||
validateReceiver()
|
||||
}
|
||||
|
||||
nameRenderer(person){
|
||||
console.log({person})
|
||||
return html`
|
||||
<vaadin-horizontal-layout style="align-items: center;display:flex" theme="spacing">
|
||||
<vaadin-avatar style="margin-right:5px" img="${person.pictureUrl}" .name="${person.displayName}"></vaadin-avatar>
|
||||
<span> ${person.displayName} </span>
|
||||
</vaadin-horizontal-layout>
|
||||
`;
|
||||
};
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<!-- <vaadin-icon @click=${()=> {
|
||||
this.isOpenLeaveModal = true
|
||||
}} class="top-bar-icon" style="margin: 0px 20px" icon="vaadin:exit" slot="icon"></vaadin-icon> -->
|
||||
<!-- Leave Group Dialog -->
|
||||
<wrapper-modal
|
||||
.removeImage=${() => {
|
||||
if(this.isLoading) return
|
||||
this.isOpenLeaveModal = false
|
||||
} }
|
||||
customStyle=${"width: 90%; max-width: 900px; height: 90%"}
|
||||
style=${(this.isOpenLeaveModal) ? "display: block" : "display: none"}>
|
||||
<div style="width: 100%;height: 100%;display: flex; flex-direction: column;background:var(--mdc-theme-surface)">
|
||||
<div style="height: 50px;display: flex; flex:0">
|
||||
<vaadin-tabs id="tabs" selected="${this.currentTab}" @selected-changed="${this._tabChanged}" style="width: 100%">
|
||||
|
||||
<vaadin-tab>Groups</vaadin-tab>
|
||||
<vaadin-tab>Group Join Requests</vaadin-tab>
|
||||
<vaadin-tab>Invites</vaadin-tab>
|
||||
<vaadin-tab>Blocked Users</vaadin-tab>
|
||||
</vaadin-tabs>
|
||||
</div>
|
||||
|
||||
<div style="width: 100%;display: flex; flex-direction: column; flex-grow: 1; overflow:auto;background:var(--mdc-theme-surface)">
|
||||
|
||||
${this.currentTab === 0 ? html`
|
||||
<div>
|
||||
|
||||
|
||||
<!-- Groups tab -->
|
||||
<!-- Search groups and be able to join -->
|
||||
<p>Search groups</p>
|
||||
<!-- Click group and it goes to that group and open right panel and settings -->
|
||||
<p>Current groups as owner</p>
|
||||
<p>Current groups as member</p>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
|
||||
</div>
|
||||
<div style="width: 100%;height: 50;display: flex; flex: 0">
|
||||
<button
|
||||
class="modal-button"
|
||||
?disabled="${this.isLoading}"
|
||||
@click=${() => this._leaveGroup(this.leaveGroupObj.groupId, this.leaveGroupObj.groupName)}
|
||||
>
|
||||
${translate("grouppage.gchange37")}
|
||||
</button>
|
||||
<button
|
||||
@click=${() => {
|
||||
this.isOpenLeaveModal= false
|
||||
}}
|
||||
class="modal-button"
|
||||
?disabled="${this.isLoading}"
|
||||
|
||||
>
|
||||
${translate("general.close")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</wrapper-modal >
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('chat-groups-management', ChatGroupsManagement);
|
@ -13,14 +13,18 @@ class ChatHead extends LitElement {
|
||||
config: { type: Object },
|
||||
chatInfo: { type: Object },
|
||||
iconName: { type: String },
|
||||
activeChatHeadUrl: { type: String }
|
||||
activeChatHeadUrl: { type: String },
|
||||
isImageLoaded: { type: Boolean },
|
||||
setActiveChatHeadUrl: {attribute: false}
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
li {
|
||||
padding: 10px 2px 20px 5px;
|
||||
|
||||
width: 100%;
|
||||
padding: 7px 5px 7px 5px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
@ -37,7 +41,7 @@ class ChatHead extends LitElement {
|
||||
.img-icon {
|
||||
float: left;
|
||||
font-size:40px;
|
||||
color: var(--black);
|
||||
color: var(--chat-group);
|
||||
}
|
||||
|
||||
.about {
|
||||
@ -76,14 +80,54 @@ class ChatHead extends LitElement {
|
||||
this.chatInfo = {}
|
||||
this.iconName = ''
|
||||
this.activeChatHeadUrl = ''
|
||||
this.isImageLoaded = false
|
||||
this.imageFetches = 0
|
||||
}
|
||||
|
||||
createImage(imageUrl) {
|
||||
const imageHTMLRes = new Image();
|
||||
imageHTMLRes.src = imageUrl;
|
||||
imageHTMLRes.style= "width:40px; height:40px; float: left; border-radius:50%";
|
||||
imageHTMLRes.onclick= () => {
|
||||
this.openDialogImage = true;
|
||||
}
|
||||
imageHTMLRes.onload = () => {
|
||||
this.isImageLoaded = true;
|
||||
}
|
||||
imageHTMLRes.onerror = () => {
|
||||
if (this.imageFetches < 4) {
|
||||
setTimeout(() => {
|
||||
this.imageFetches = this.imageFetches + 1;
|
||||
imageHTMLRes.src = imageUrl;
|
||||
}, 500);
|
||||
} else {
|
||||
|
||||
|
||||
this.isImageLoaded = false
|
||||
}
|
||||
};
|
||||
return imageHTMLRes;
|
||||
}
|
||||
|
||||
render() {
|
||||
let avatarImg = '';
|
||||
let backupAvatarImg = ''
|
||||
if(this.chatInfo.name){
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
|
||||
const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${this.chatInfo.name}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`;
|
||||
avatarImg= this.createImage(avatarUrl)
|
||||
|
||||
}
|
||||
|
||||
return html`
|
||||
<li @click=${() => this.getUrl(this.chatInfo.url)} class="clearfix ${this.activeChatHeadUrl === this.chatInfo.url ? 'active' : ''}">
|
||||
<mwc-icon class="img-icon">account_circle</mwc-icon>
|
||||
${this.isImageLoaded ? html`${avatarImg}` : html`` }
|
||||
${!this.isImageLoaded && !this.chatInfo.name && !this.chatInfo.groupName ? html`<mwc-icon class="img-icon">account_circle</mwc-icon>` : html`` }
|
||||
${!this.isImageLoaded && this.chatInfo.name ? html`<div style="width:40px; height:40px; float: left; border-radius:50%; background: ${this.activeChatHeadUrl === this.chatInfo.url ? 'var(--chatHeadBgActive)' : 'var(--chatHeadBg)' }; color: ${this.activeChatHeadUrl === this.chatInfo.url ? 'var(--chatHeadTextActive)' : 'var(--chatHeadText)' }; font-weight:bold; display: flex; justify-content: center; align-items: center; text-transform: capitalize">${this.chatInfo.name.charAt(0)}</div>`: ''}
|
||||
${!this.isImageLoaded && this.chatInfo.groupName ? html`<div style="width:40px; height:40px; float: left; border-radius:50%; background: ${this.activeChatHeadUrl === this.chatInfo.url ? 'var(--chatHeadBgActive)' : 'var(--chatHeadBg)' }; color: ${this.activeChatHeadUrl === this.chatInfo.url ? 'var(--chatHeadTextActive)' : 'var(--chatHeadText)' }; font-weight:bold; display: flex; justify-content: center; align-items: center; text-transform: capitalize">${this.chatInfo.groupName.charAt(0)}</div>`: ''}
|
||||
<div class="about">
|
||||
<div class="name"><span style="float:left; padding-left: 8px; color: var(--black);">${this.chatInfo.groupName ? this.chatInfo.groupName : this.chatInfo.name !== undefined ? this.chatInfo.name : this.chatInfo.address.substr(0, 15)} </span> <mwc-icon style="float:right; padding: 0 1rem; color: var(--black);">${this.chatInfo.groupId !== undefined ? 'lock_open' : 'lock'}</mwc-icon> </div>
|
||||
<div class="name"><span style="float:left; padding-left: 8px; color: var(--chat-group);">${this.chatInfo.groupName ? this.chatInfo.groupName : this.chatInfo.name !== undefined ? this.chatInfo.name : this.chatInfo.address.substr(0, 15)} </span> <mwc-icon style="float:right; padding: 0 1rem; color: var(--chat-group);">${this.chatInfo.groupId !== undefined ? 'lock_open' : 'lock'}</mwc-icon> </div>
|
||||
</div>
|
||||
</li>
|
||||
`
|
||||
@ -108,8 +152,19 @@ class ChatHead extends LitElement {
|
||||
parentEpml.imReady()
|
||||
}
|
||||
|
||||
shouldUpdate(changedProperties) {
|
||||
if(changedProperties.has('activeChatHeadUrl')){
|
||||
return true
|
||||
}
|
||||
if(changedProperties.has('chatInfo')){
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
getUrl(chatUrl) {
|
||||
this.onPageNavigation(`/app/q-chat/${chatUrl}`)
|
||||
this.setActiveChatHeadUrl(chatUrl)
|
||||
}
|
||||
|
||||
onPageNavigation(pageUrl) {
|
||||
|
268
qortal-ui-plugins/plugins/core/components/ChatLeaveGroup.js
Normal file
@ -0,0 +1,268 @@
|
||||
import { LitElement, html, css } from 'lit';
|
||||
import { render } from 'lit/html.js';
|
||||
import { get, translate } from 'lit-translate';
|
||||
import { Epml } from '../../../epml';
|
||||
import snackbar from './snackbar.js'
|
||||
import '@material/mwc-button';
|
||||
import '@material/mwc-dialog';
|
||||
import '@polymer/paper-spinner/paper-spinner-lite.js'
|
||||
import '@material/mwc-icon';
|
||||
import './WrapperModal';
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
||||
|
||||
class ChatLeaveGroup extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
isLoading: { type: Boolean },
|
||||
isOpenLeaveModal: {type: Boolean},
|
||||
leaveGroupObj: { type: Object },
|
||||
error: {type: Boolean},
|
||||
message: {type: String},
|
||||
chatHeads: {type: Array},
|
||||
setActiveChatHeadUrl: {attribute: false},
|
||||
selectedAddress: {attribute: Object}
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.isLoading = false;
|
||||
this.isOpenLeaveModal = false
|
||||
this.leaveGroupObj = {}
|
||||
this.fee = null
|
||||
this.error = false
|
||||
this.message = ''
|
||||
this.chatHeads = []
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
.top-bar-icon {
|
||||
cursor: pointer;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
transition: .2s all;
|
||||
}
|
||||
.top-bar-icon:hover {
|
||||
color: var(--black)
|
||||
}
|
||||
.modal-button {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 16px;
|
||||
color: var(--mdc-theme-primary);
|
||||
background-color: transparent;
|
||||
padding: 8px 10px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
|
||||
}
|
||||
|
||||
async unitFee() {
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
const url = `${nodeUrl}/transactions/unitfee?txType=LEAVE_GROUP`
|
||||
let fee = null
|
||||
|
||||
try {
|
||||
const res = await fetch(url)
|
||||
const data = await res.json()
|
||||
fee = (Number(data) / 1e8).toFixed(3)
|
||||
} catch (error) {
|
||||
fee = null
|
||||
}
|
||||
|
||||
return fee
|
||||
}
|
||||
|
||||
timeIsoString(timestamp) {
|
||||
let myTimestamp = timestamp === undefined ? 1587560082346 : timestamp
|
||||
let time = new Date(myTimestamp)
|
||||
return time.toISOString()
|
||||
}
|
||||
|
||||
resetDefaultSettings() {
|
||||
this.error = false
|
||||
this.message = ''
|
||||
this.isLoading = false
|
||||
}
|
||||
|
||||
renderErr9Text() {
|
||||
return html`${translate("grouppage.gchange49")}`
|
||||
}
|
||||
|
||||
async confirmRelationship() {
|
||||
|
||||
|
||||
let interval = null
|
||||
let stop = false
|
||||
const getAnswer = async () => {
|
||||
const currentChats = this.chatHeads
|
||||
|
||||
if (!stop) {
|
||||
stop = true;
|
||||
try {
|
||||
const findGroup = currentChats.find((item)=> item.groupId === this.leaveGroupObj.groupId)
|
||||
if (!findGroup) {
|
||||
clearInterval(interval)
|
||||
this.isLoading = false
|
||||
this.isOpenLeaveModal= false
|
||||
this.setActiveChatHeadUrl('')
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
}
|
||||
stop = false
|
||||
}
|
||||
};
|
||||
interval = setInterval(getAnswer, 5000);
|
||||
}
|
||||
|
||||
async _leaveGroup(groupId, groupName) {
|
||||
// Reset Default Settings...
|
||||
this.resetDefaultSettings()
|
||||
|
||||
const leaveFeeInput = await this.unitFee()
|
||||
if(!leaveFeeInput){
|
||||
throw Error()
|
||||
}
|
||||
this.isLoading = true
|
||||
|
||||
// Get Last Ref
|
||||
const getLastRef = async () => {
|
||||
let myRef = await parentEpml.request('apiCall', {
|
||||
type: 'api',
|
||||
url: `/addresses/lastreference/${this.selectedAddress.address}`
|
||||
})
|
||||
return myRef
|
||||
};
|
||||
|
||||
const validateReceiver = async () => {
|
||||
let lastRef = await getLastRef();
|
||||
let myTransaction = await makeTransactionRequest(lastRef)
|
||||
getTxnRequestResponse(myTransaction)
|
||||
|
||||
}
|
||||
|
||||
// Make Transaction Request
|
||||
const makeTransactionRequest = async (lastRef) => {
|
||||
let groupdialog3 = get("transactions.groupdialog3")
|
||||
let groupdialog4 = get("transactions.groupdialog4")
|
||||
let myTxnrequest = await parentEpml.request('transaction', {
|
||||
type: 32,
|
||||
nonce: this.selectedAddress.nonce,
|
||||
params: {
|
||||
fee: leaveFeeInput,
|
||||
registrantAddress: this.selectedAddress.address,
|
||||
rGroupName: groupName,
|
||||
rGroupId: groupId,
|
||||
lastReference: lastRef,
|
||||
groupdialog3: groupdialog3,
|
||||
groupdialog4: groupdialog4,
|
||||
}
|
||||
})
|
||||
return myTxnrequest
|
||||
}
|
||||
|
||||
const getTxnRequestResponse = (txnResponse) => {
|
||||
|
||||
if (txnResponse.success === false && txnResponse.message) {
|
||||
this.error = true
|
||||
this.message = txnResponse.message
|
||||
throw new Error(txnResponse)
|
||||
} else if (txnResponse.success === true && !txnResponse.data.error) {
|
||||
this.message = this.renderErr9Text()
|
||||
this.error = false
|
||||
this.confirmRelationship()
|
||||
} else {
|
||||
this.error = true
|
||||
this.message = txnResponse.data.message
|
||||
throw new Error(txnResponse)
|
||||
}
|
||||
}
|
||||
validateReceiver()
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<vaadin-icon @click=${()=> {
|
||||
this.isOpenLeaveModal = true
|
||||
}} class="top-bar-icon" style="margin: 0px 20px" icon="vaadin:exit" slot="icon"></vaadin-icon>
|
||||
<!-- Leave Group Dialog -->
|
||||
<wrapper-modal
|
||||
.removeImage=${() => {
|
||||
if(this.isLoading) return
|
||||
this.isOpenLeaveModal = false
|
||||
} }
|
||||
style=${(this.isOpenLeaveModal) ? "display: block" : "display: none"}>
|
||||
<div style="text-align:center">
|
||||
<h1>${translate("grouppage.gchange35")}</h1>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div class="itemList">
|
||||
<span class="title">${translate("grouppage.gchange4")}</span>
|
||||
<br>
|
||||
<div><span>${this.leaveGroupObj.groupName}</span></div>
|
||||
|
||||
<span class="title">${translate("grouppage.gchange5")}</span>
|
||||
<br>
|
||||
<div><span>${this.leaveGroupObj.description}</span></div>
|
||||
|
||||
<span class="title">${translate("grouppage.gchange10")}</span>
|
||||
<br>
|
||||
<div><span>${this.leaveGroupObj.owner}</span></div>
|
||||
|
||||
<span class="title">${translate("grouppage.gchange31")}</span>
|
||||
<br>
|
||||
<div><span><time-ago datetime=${this.timeIsoString(this.leaveGroupObj.created)}></time-ago></span></div>
|
||||
|
||||
${!this.leaveGroupObj.updated ? "" : html`<span class="title">${translate("grouppage.gchange32")}</span>
|
||||
<br>
|
||||
<div><span><time-ago datetime=${this.timeIsoString(this.leaveGroupObj.updated)}></time-ago></span></div>`}
|
||||
</div>
|
||||
|
||||
<div style="text-align:right; height:36px;">
|
||||
<span ?hidden="${!this.isLoading}">
|
||||
<!-- loading message -->
|
||||
${translate("grouppage.gchange36")}
|
||||
<paper-spinner-lite
|
||||
style="margin-top:12px;"
|
||||
?active="${this.isLoading}"
|
||||
alt="Leaving"
|
||||
>
|
||||
</paper-spinner-lite>
|
||||
</span>
|
||||
<span ?hidden=${this.message === ''} style="${this.error ? 'color:red;' : ''}">
|
||||
${this.message}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<button
|
||||
class="modal-button"
|
||||
?disabled="${this.isLoading}"
|
||||
@click=${() => this._leaveGroup(this.leaveGroupObj.groupId, this.leaveGroupObj.groupName)}
|
||||
>
|
||||
${translate("grouppage.gchange37")}
|
||||
</button>
|
||||
<button
|
||||
@click=${() => {
|
||||
this.isOpenLeaveModal= false
|
||||
}}
|
||||
class="modal-button"
|
||||
?disabled="${this.isLoading}"
|
||||
|
||||
>
|
||||
${translate("general.close")}
|
||||
</button>
|
||||
</wrapper-modal >
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('chat-leave-group', ChatLeaveGroup);
|
@ -92,11 +92,11 @@ class ChatModals extends LitElement {
|
||||
// Send Private Message
|
||||
|
||||
_sendMessage() {
|
||||
this.isLoading = true
|
||||
this.isLoading = true;
|
||||
|
||||
const recipient = this.shadowRoot.getElementById('sendTo').value
|
||||
const messageBox = this.shadowRoot.getElementById('messageBox')
|
||||
const messageText = messageBox.value
|
||||
const recipient = this.shadowRoot.getElementById('sendTo').value;
|
||||
const messageBox = this.shadowRoot.getElementById('messageBox');
|
||||
const messageText = messageBox.value;
|
||||
|
||||
if (recipient.length === 0) {
|
||||
this.isLoading = false
|
||||
@ -105,22 +105,21 @@ class ChatModals extends LitElement {
|
||||
} else {
|
||||
this.sendMessage()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async sendMessage() {
|
||||
this.isLoading = true
|
||||
|
||||
const _recipient = this.shadowRoot.getElementById('sendTo').value
|
||||
const messageBox = this.shadowRoot.getElementById('messageBox')
|
||||
const messageText = messageBox.value
|
||||
let recipient
|
||||
this.isLoading = true;
|
||||
const _recipient = this.shadowRoot.getElementById('sendTo').value;
|
||||
const messageBox = this.shadowRoot.getElementById('messageBox');
|
||||
const messageText = messageBox.value;
|
||||
let recipient;
|
||||
|
||||
const validateName = async (receiverName) => {
|
||||
let myRes
|
||||
let myRes;
|
||||
let myNameRes = await parentEpml.request('apiCall', {
|
||||
type: 'api',
|
||||
url: `/names/${receiverName}`
|
||||
})
|
||||
});
|
||||
|
||||
if (myNameRes.error === 401) {
|
||||
myRes = false
|
||||
@ -128,7 +127,7 @@ class ChatModals extends LitElement {
|
||||
myRes = myNameRes
|
||||
}
|
||||
|
||||
return myRes
|
||||
return myRes;
|
||||
}
|
||||
|
||||
const myNameRes = await validateName(_recipient)
|
||||
@ -139,7 +138,6 @@ class ChatModals extends LitElement {
|
||||
|
||||
recipient = myNameRes.owner
|
||||
}
|
||||
|
||||
let _reference = new Uint8Array(64);
|
||||
window.crypto.getRandomValues(_reference);
|
||||
|
||||
@ -175,7 +173,13 @@ class ChatModals extends LitElement {
|
||||
};
|
||||
|
||||
const sendMessageRequest = async (isEncrypted, _publicKey) => {
|
||||
|
||||
const messageObject = {
|
||||
messageText,
|
||||
images: [''],
|
||||
repliedTo: '',
|
||||
version: 1
|
||||
}
|
||||
const stringifyMessageObject = JSON.stringify(messageObject)
|
||||
let chatResponse = await parentEpml.request('chat', {
|
||||
type: 18,
|
||||
nonce: this.selectedAddress.nonce,
|
||||
@ -184,7 +188,7 @@ class ChatModals extends LitElement {
|
||||
recipient: recipient,
|
||||
recipientPublicKey: _publicKey,
|
||||
hasChatReference: 0,
|
||||
message: messageText,
|
||||
message: stringifyMessageObject,
|
||||
lastReference: reference,
|
||||
proofOfWorkNonce: 0,
|
||||
isEncrypted: isEncrypted,
|
||||
@ -361,7 +365,10 @@ class ChatModals extends LitElement {
|
||||
<p style='margin-bottom:0;'>
|
||||
<textarea class='textarea' @keydown=${(e) => this._textArea(e)} ?disabled=${this.isLoading} id='messageBox' placeholder='${translate('welcomepage.wcchange5')}' rows='1'></textarea>
|
||||
</p>
|
||||
<mwc-button ?disabled='${this.isLoading}' slot='primaryAction' @click=${this._sendMessage}>${translate('welcomepage.wcchange6')}
|
||||
<mwc-button ?disabled='${this.isLoading}' slot='primaryAction' @click=${() => {
|
||||
this._sendMessage();
|
||||
}
|
||||
}>${translate('welcomepage.wcchange6')}
|
||||
</mwc-button>
|
||||
<mwc-button
|
||||
?disabled='${this.isLoading}'
|
||||
|
306
qortal-ui-plugins/plugins/core/components/ChatRightPanel.js
Normal file
@ -0,0 +1,306 @@
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { render } from "lit/html.js";
|
||||
import { get, translate } from "lit-translate";
|
||||
import { Epml } from "../../../epml";
|
||||
import { getUserNameFromAddress } from "../../utils/getUserNameFromAddress";
|
||||
import snackbar from "./snackbar.js";
|
||||
import "@material/mwc-button";
|
||||
import "@material/mwc-dialog";
|
||||
import "@polymer/paper-spinner/paper-spinner-lite.js";
|
||||
import '@polymer/paper-progress/paper-progress.js';
|
||||
import "@material/mwc-icon";
|
||||
import '@vaadin/button';
|
||||
import "./WrapperModal";
|
||||
import "./TipUser"
|
||||
import "./UserInfo/UserInfo";
|
||||
|
||||
class ChatRightPanel extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
leaveGroupObj: { type: Object },
|
||||
error: { type: Boolean },
|
||||
chatHeads: { type: Array },
|
||||
groupAdmin: { attribute: false },
|
||||
groupMembers: { attribute: false },
|
||||
selectedHead: { type: Object },
|
||||
toggle: { attribute: false },
|
||||
getMoreMembers:{ attribute: false },
|
||||
setOpenPrivateMessage: { attribute: false },
|
||||
userName: { type: String },
|
||||
walletBalance: { type: Number },
|
||||
sendMoneyLoading: { type: Boolean },
|
||||
btnDisable: { type: Boolean },
|
||||
errorMessage: { type: String },
|
||||
successMessage: { type: String },
|
||||
setOpenTipUser: { attribute: false },
|
||||
setOpenUserInfo: { attribute: false },
|
||||
setUserName: { attribute: false },
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.leaveGroupObj = {}
|
||||
this.leaveFee = 0.001
|
||||
this.error = false
|
||||
this.chatHeads = []
|
||||
this.groupAdmin = []
|
||||
this.groupMembers = []
|
||||
this.observerHandler = this.observerHandler.bind(this)
|
||||
this.viewElement = ''
|
||||
this.downObserverElement = ''
|
||||
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address
|
||||
this.sendMoneyLoading = false
|
||||
this.btnDisable = false
|
||||
this.errorMessage = ""
|
||||
this.successMessage = ""
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
.top-bar-icon {
|
||||
cursor: pointer;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
transition: 0.2s all;
|
||||
}
|
||||
|
||||
.top-bar-icon:hover {
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.modal-button {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 16px;
|
||||
color: var(--mdc-theme-primary);
|
||||
background-color: transparent;
|
||||
padding: 8px 10px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.close-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
height: 50px;
|
||||
flex:0
|
||||
|
||||
}
|
||||
|
||||
.container-body {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
overflow:auto;
|
||||
margin-top: 5px;
|
||||
padding: 0px 6px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.container-body::-webkit-scrollbar-track {
|
||||
background-color: whitesmoke;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.container-body::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
border-radius: 7px;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
.container-body::-webkit-scrollbar-thumb {
|
||||
background-color: rgb(180, 176, 176);
|
||||
border-radius: 7px;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.container-body::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgb(148, 146, 146);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--black);
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.chat-right-panel-label {
|
||||
font-family: Montserrat, sans-serif;
|
||||
color: var(--group-header);
|
||||
padding: 5px;
|
||||
font-size: 13px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.group-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.group-name {
|
||||
font-family: Raleway, sans-serif;
|
||||
font-size: 20px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
text-align: center;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.group-description {
|
||||
font-family: Roboto, sans-serif;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
letter-spacing: 0.3px;
|
||||
font-weight: 300;
|
||||
font-size: 14px;
|
||||
margin-top: 15px;
|
||||
word-break: break-word;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.group-subheader {
|
||||
font-family: Montserrat, sans-serif;
|
||||
font-size: 14px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
}
|
||||
|
||||
.group-data {
|
||||
font-family: Roboto, sans-serif;
|
||||
letter-spacing: 0.3px;
|
||||
font-weight: 300;
|
||||
font-size: 14px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.viewElement = this.shadowRoot.getElementById('viewElement');
|
||||
this.downObserverElement = this.shadowRoot.getElementById('downObserver');
|
||||
this.elementObserver();
|
||||
}
|
||||
|
||||
async updated(changedProperties) {
|
||||
if (changedProperties && changedProperties.has('selectedHead')) {
|
||||
if (this.selectedHead !== {}) {
|
||||
const userName = await getUserNameFromAddress(this.selectedHead.address);
|
||||
this.userName = userName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
elementObserver() {
|
||||
const options = {
|
||||
root: this.viewElement,
|
||||
rootMargin: '0px',
|
||||
threshold: 1
|
||||
}
|
||||
// identify an element to observe
|
||||
const elementToObserve = this.downObserverElement;
|
||||
// passing it a callback function
|
||||
const observer = new IntersectionObserver(this.observerHandler, options);
|
||||
// call `observe()` on that MutationObserver instance,
|
||||
// passing it the element to observe, and the options object
|
||||
observer.observe(elementToObserve);
|
||||
}
|
||||
|
||||
observerHandler(entries) {
|
||||
if (!entries[0].isIntersecting) {
|
||||
return
|
||||
} else {
|
||||
if(this.groupMembers.length < 20){
|
||||
return
|
||||
}
|
||||
console.log('this.leaveGroupObjp', this.leaveGroupObj)
|
||||
this.getMoreMembers(this.leaveGroupObj.groupId)
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const owner = this.groupAdmin.filter((admin)=> admin.address === this.leaveGroupObj.owner)
|
||||
return html`
|
||||
<div class="container">
|
||||
<div class="close-row" style="margin-top: 15px">
|
||||
<vaadin-icon class="top-bar-icon" @click=${()=> this.toggle(false)} style="margin: 0px 10px" icon="vaadin:close" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
<div id="viewElement" class="container-body">
|
||||
<p class="group-name">${this.leaveGroupObj && this.leaveGroupObj.groupName}</p>
|
||||
<div class="group-info">
|
||||
<p class="group-description">${this.leaveGroupObj && this.leaveGroupObj.description}</p>
|
||||
<p class="group-subheader">Members: <span class="group-data">${this.leaveGroupObj && this.leaveGroupObj.memberCount}</span></p>
|
||||
|
||||
<p class="group-subheader">Date created : <span class="group-data">${new Date(this.leaveGroupObj.created).toLocaleDateString("en-US")}</span></p>
|
||||
</div>
|
||||
<br />
|
||||
<p class="chat-right-panel-label">GROUP OWNER</p>
|
||||
${owner.map((item) => {
|
||||
return html`<chat-side-nav-heads
|
||||
activeChatHeadUrl=""
|
||||
.setActiveChatHeadUrl=${(val) => {
|
||||
if (val.address === this.myAddress) return;
|
||||
console.log({ val });
|
||||
this.selectedHead = val;
|
||||
this.setOpenUserInfo(true);
|
||||
this.setUserName({
|
||||
sender: val.address,
|
||||
senderName: val.name ? val.name : ""
|
||||
});
|
||||
}}
|
||||
chatInfo=${JSON.stringify(item)}
|
||||
></chat-side-nav-heads>`
|
||||
})}
|
||||
<p class="chat-right-panel-label">ADMINS</p>
|
||||
${this.groupAdmin.map((item) => {
|
||||
return html`<chat-side-nav-heads
|
||||
activeChatHeadUrl=""
|
||||
.setActiveChatHeadUrl=${(val) => {
|
||||
if (val.address === this.myAddress) return;
|
||||
console.log({ val });
|
||||
this.selectedHead = val;
|
||||
this.setOpenUserInfo(true);
|
||||
this.setUserName({
|
||||
sender: val.address,
|
||||
senderName: val.name ? val.name : ""
|
||||
});
|
||||
}}
|
||||
chatInfo=${JSON.stringify(item)}
|
||||
></chat-side-nav-heads>`
|
||||
})}
|
||||
<p class="chat-right-panel-label">MEMBERS</p>
|
||||
${this.groupMembers.map((item) => {
|
||||
return html`<chat-side-nav-heads
|
||||
activeChatHeadUrl=""
|
||||
.setActiveChatHeadUrl=${(val) => {
|
||||
if (val.address === this.myAddress) return;
|
||||
console.log({ val });
|
||||
this.selectedHead = val;
|
||||
this.setOpenUserInfo(true);
|
||||
this.setUserName({
|
||||
sender: val.address,
|
||||
senderName: val.name ? val.name : ""
|
||||
});
|
||||
}}
|
||||
chatInfo=${JSON.stringify(item)}
|
||||
></chat-side-nav-heads>`
|
||||
})}
|
||||
<div id='downObserver'></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define("chat-right-panel", ChatRightPanel)
|
@ -15,6 +15,12 @@ export const chatStyles = css`
|
||||
scrollbar-color: var(--thumbBG) var(--scrollbarBG);
|
||||
--mdc-theme-primary: rgb(3, 169, 244);
|
||||
--mdc-theme-secondary: var(--mdc-theme-primary);
|
||||
--mdc-dialog-max-width: 85vw;
|
||||
--mdc-dialog-max-height: 95vh;
|
||||
}
|
||||
|
||||
* :focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar-track {
|
||||
@ -35,110 +41,214 @@ export const chatStyles = css`
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.last-message-ref {
|
||||
position: fixed;
|
||||
font-size: 20px;
|
||||
right: 40px;
|
||||
bottom: 100px;
|
||||
width: 50;
|
||||
height: 50;
|
||||
z-index: 5;
|
||||
opacity: 0;
|
||||
color: black;
|
||||
background-color: white;
|
||||
border-radius: 50%;
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.last-message-ref:hover {
|
||||
cursor: pointer;
|
||||
transform: scale(1.1);
|
||||
padding: 20px 17px;
|
||||
}
|
||||
|
||||
.chat-list {
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
height: 92vh;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.message-data {
|
||||
width: 92%;
|
||||
margin-bottom: 15px;
|
||||
margin-left: 50px;
|
||||
margin-left: 55px;
|
||||
}
|
||||
|
||||
.message-data-name {
|
||||
color: var(--black);
|
||||
user-select: none;
|
||||
color: #03a9f4;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.forwarded-text {
|
||||
user-select: none;
|
||||
color: #03a9f4;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.message-data-forward {
|
||||
user-select: none;
|
||||
color: var(--mainmenutext);
|
||||
margin-bottom: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.message-data-my-name {
|
||||
color: #cf21e8;
|
||||
text-shadow: 0 0 3px #cf21e8;
|
||||
}
|
||||
|
||||
.message-data-time {
|
||||
color: #a8aab1;
|
||||
color: #888888;
|
||||
font-size: 13px;
|
||||
padding-left: 6px;
|
||||
padding-bottom: 4px;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.message-data-level {
|
||||
color: #03a9f4;
|
||||
.message-data-time-hidden {
|
||||
visibility: hidden;
|
||||
transition: all 0.1s ease-in-out;
|
||||
color: #888888;
|
||||
font-size: 13px;
|
||||
padding-left: 8px;
|
||||
padding-bottom: 4px;
|
||||
user-select: none;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.message-user-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.chat-bubble-container {
|
||||
display:flex;
|
||||
gap: 7px;
|
||||
}
|
||||
|
||||
.message-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message {
|
||||
color: black;
|
||||
padding: 12px 10px;
|
||||
.message-subcontainer1 {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.message-subcontainer2 {
|
||||
position: relative;
|
||||
display: flex;
|
||||
background-color: var(--chat-bubble-bg);
|
||||
flex-grow: 0;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
border-radius: 5px;
|
||||
padding: 12px 15px 4px 15px;
|
||||
width: fit-content;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.message-triangle {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message-triangle:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: -9px;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 0px 0px 7px 9px;
|
||||
border-color: transparent transparent var(--chat-bubble-bg) transparent;
|
||||
}
|
||||
|
||||
.message-reactions {
|
||||
background-color: transparent;
|
||||
width: calc(100% - 54px);
|
||||
margin-left: 54px;
|
||||
}
|
||||
|
||||
.original-message {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
line-height: 19px;
|
||||
white-space: pre-line;
|
||||
word-wrap: break-word;
|
||||
user-select: text;
|
||||
font-size: 15px;
|
||||
width: 90%;
|
||||
border-radius: 5px;
|
||||
padding: 8px 5px 8px 25px;
|
||||
margin-bottom: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.original-message:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 10px;
|
||||
height: 75%;
|
||||
width: 2.6px;
|
||||
background-color: var(--mdc-theme-primary);
|
||||
}
|
||||
|
||||
.original-message-sender {
|
||||
margin: 0 0 5px 0;
|
||||
color: var(--mdc-theme-primary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.replied-message {
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 300px;
|
||||
max-height: 40px;
|
||||
}
|
||||
.replied-message p {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.message {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
line-height: 19px;
|
||||
overflow-wrap: anywhere;
|
||||
-webkit-user-select: text;
|
||||
-moz-user-select: text;
|
||||
-ms-user-select: text;
|
||||
user-select: text;
|
||||
font-size: 16px;
|
||||
border-radius: 7px;
|
||||
margin-bottom: 20px;
|
||||
width: 90%;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.message:after {
|
||||
bottom: 100%;
|
||||
left: 93%;
|
||||
border: solid transparent;
|
||||
content: " ";
|
||||
height: 0;
|
||||
width: 0;
|
||||
position: absolute;
|
||||
white-space: pre-line;
|
||||
word-wrap: break-word;
|
||||
pointer-events: none;
|
||||
border-bottom-color: #ddd;
|
||||
border-width: 10px;
|
||||
margin-left: -10px;
|
||||
.message-data-avatar {
|
||||
margin: 0px 10px 0px 3px;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.message-parent {
|
||||
padding: 3px;
|
||||
background: rgba(245, 245, 245, 0);
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.message-parent:hover {
|
||||
background: var(--chat-bubble);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.message-parent:hover .chat-hover {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.message-parent:hover .message{
|
||||
filter:brightness(0.90);
|
||||
.message-parent:hover .message-data-time-hidden {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.chat-hover {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: -38px;
|
||||
left: 88.2%;
|
||||
top: -25px;
|
||||
right: 5px;
|
||||
}
|
||||
|
||||
.emoji {
|
||||
@ -149,26 +259,6 @@ export const chatStyles = css`
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.my-message {
|
||||
background: #d1d1d1;
|
||||
border: 2px solid #eeeeee;
|
||||
}
|
||||
|
||||
.my-message:after {
|
||||
border-bottom-color: #d1d1d1;
|
||||
left: 7%;
|
||||
}
|
||||
|
||||
.other-message {
|
||||
background: #f1f1f1;
|
||||
border: 2px solid #dedede;
|
||||
}
|
||||
|
||||
.other-message:after {
|
||||
border-bottom-color: #f1f1f1;
|
||||
left: 7%;
|
||||
}
|
||||
|
||||
.align-left {
|
||||
text-align: left;
|
||||
}
|
||||
@ -202,25 +292,29 @@ export const chatStyles = css`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 5px;
|
||||
background-color: white;
|
||||
border: 1px solid #dad9d9;
|
||||
background-color: var(--chat-menu-bg);
|
||||
border: 1px solid var(--chat-menu-outline);
|
||||
border-radius: 5px;
|
||||
height:100%;
|
||||
width: 100px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.container:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
padding: 5px 7px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: var(--chat-menu-icon);
|
||||
}
|
||||
|
||||
.menu-icon:hover {
|
||||
background-color: #dad9d9;
|
||||
border-radius: 5px;
|
||||
background-color: var(--chat-menu-icon-hover);
|
||||
transition: all 0.1s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
@ -231,11 +325,12 @@ export const chatStyles = css`
|
||||
|
||||
.tooltip:before {
|
||||
content: attr(data-text);
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: -47px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 90px;
|
||||
width: auto;
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
background:#fff;
|
||||
@ -244,7 +339,8 @@ export const chatStyles = css`
|
||||
box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
|
||||
font-size: 12px;
|
||||
z-index: 5;
|
||||
display: none;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tooltip:hover:before {
|
||||
@ -269,17 +365,299 @@ export const chatStyles = css`
|
||||
.block-user-container {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: -48px;
|
||||
left: -5px;
|
||||
}
|
||||
|
||||
.block-user {
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
padding: 5px 7px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: var(--chat-menu-icon);
|
||||
justify-content: space-evenly;
|
||||
border: 1px solid rgb(218, 217, 217);
|
||||
border-radius: 5px;
|
||||
background-color: white;
|
||||
width: 100%;
|
||||
background-color: var(--chat-menu-bg);
|
||||
width: 150px;
|
||||
height: 32px;
|
||||
padding: 3px 8px;
|
||||
box-shadow: rgba(77, 77, 82, 0.2) 0px 7px 29px 0px;
|
||||
}
|
||||
|
||||
.block-user:hover {
|
||||
cursor:pointer;
|
||||
background-color: var(--block-user-bg-hover);
|
||||
transition: all 0.1s ease-in-out 0s;
|
||||
}
|
||||
|
||||
.reactions-bg {
|
||||
background-color: #d5d5d5;
|
||||
border-radius: 10px;
|
||||
padding: 5px;
|
||||
color: black;
|
||||
margin-right: 10px;
|
||||
transition: all 0.1s ease-in-out;
|
||||
border: 0.5px solid transparent;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.reactions-bg:hover {
|
||||
border: 0.5px solid var(--reaction-bubble-outline);
|
||||
}
|
||||
|
||||
.image-container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.message-data-level {
|
||||
height: 21px;
|
||||
width: 21px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.defaultSize {
|
||||
width: 45vh;
|
||||
height: 40vh;
|
||||
}
|
||||
|
||||
.image-deleted-msg {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 14px;
|
||||
font-style: italic;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
margin: 0;
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.image-delete-icon {
|
||||
margin-left: 5px;
|
||||
height: 20px;
|
||||
cursor: pointer;
|
||||
visibility: hidden;
|
||||
transition: .2s all;
|
||||
opacity: 0.8;
|
||||
color: rgb(228, 222, 222);
|
||||
padding-left: 7px;
|
||||
}
|
||||
|
||||
.image-delete-icon:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.message-parent:hover .image-delete-icon {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.imageContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
}
|
||||
.spinnerContainer {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
.delete-image-msg {
|
||||
font-family: Livvic, sans-serif;
|
||||
font-size: 20px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
letter-spacing: 0.3px;
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.modal-button-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-button {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 16px;
|
||||
color: var(--mdc-theme-primary);
|
||||
background-color: transparent;
|
||||
padding: 8px 10px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.modal-button-red {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 16px;
|
||||
color: #F44336;
|
||||
background-color: transparent;
|
||||
padding: 8px 10px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.modal-button-red:hover {
|
||||
cursor: pointer;
|
||||
background-color: #f4433663;
|
||||
}
|
||||
|
||||
.modal-button:hover {
|
||||
cursor: pointer;
|
||||
background-color: #03a8f475;
|
||||
}
|
||||
|
||||
#messageContent p {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
#messageContent p mark {
|
||||
background-color: #ffe066;
|
||||
border-radius: 0.25em;
|
||||
box-decoration-break: clone;
|
||||
padding: 0.125em 0;
|
||||
}
|
||||
|
||||
#messageContent > * + * {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
#messageContent ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
#messageContent h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
#messageContent code {
|
||||
background-color: rgba(#616161, 0.1);
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
#messageContent pre {
|
||||
background: #0D0D0D;
|
||||
color: #FFF;
|
||||
font-family: 'JetBrainsMono', monospace;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
#messageContent pre code {
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
|
||||
#messageContent img {
|
||||
width: 1.7em;
|
||||
height: 1.5em;
|
||||
margin: 0px;
|
||||
|
||||
}
|
||||
|
||||
#messageContent blockquote {
|
||||
padding-left: 1rem;
|
||||
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||
}
|
||||
|
||||
#messageContent hr {
|
||||
border: none;
|
||||
border-top: 2px solid rgba(#0D0D0D, 0.1);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.replied-message p {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
.replied-message > * + * {
|
||||
margin-top: 0.75em;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.replied-message ul,
|
||||
ol {
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.replied-message h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.replied-message code {
|
||||
background-color: rgba(#616161, 0.1);
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.replied-message pre {
|
||||
background: #0D0D0D;
|
||||
color: #FFF;
|
||||
font-family: 'JetBrainsMono', monospace;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 0.5rem;
|
||||
white-space: pre-wrap;
|
||||
margin: 0px;
|
||||
}
|
||||
.replied-message pre code {
|
||||
color: inherit;
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
|
||||
.replied-message img {
|
||||
width: 1.7em;
|
||||
height: 1.5em;
|
||||
margin: 0px;
|
||||
|
||||
}
|
||||
|
||||
.replied-message blockquote {
|
||||
padding-left: 1rem;
|
||||
border-left: 2px solid rgba(#0D0D0D, 0.1);
|
||||
}
|
||||
|
||||
.replied-message hr {
|
||||
border: none;
|
||||
border-top: 2px solid rgba(#0D0D0D, 0.1);
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.edited-message-style {
|
||||
font-family: "Work Sans", sans-serif;
|
||||
font-style: italic;
|
||||
font-size: 13px;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.blink-bg{
|
||||
border-radius: 8px;
|
||||
animation: blinkingBackground 3s;
|
||||
}
|
||||
@keyframes blinkingBackground{
|
||||
0% { background-color: rgba(var(--menuactivergb), 1)}
|
||||
|
||||
100% { background-color:rgba(var(--menuactivergb), 0)}
|
||||
}
|
||||
|
||||
|
||||
`
|
||||
|
@ -5,26 +5,54 @@ import { translate, get } from 'lit-translate';
|
||||
import {unsafeHTML} from 'lit/directives/unsafe-html.js';
|
||||
import { chatStyles } from './ChatScroller-css.js'
|
||||
import { Epml } from "../../../epml";
|
||||
import { cropAddress } from "../../utils/cropAddress";
|
||||
import './LevelFounder.js';
|
||||
import './NameMenu.js';
|
||||
import './ChatModals.js';
|
||||
import './WrapperModal';
|
||||
import "./UserInfo/UserInfo";
|
||||
import '@vaadin/icons';
|
||||
import '@vaadin/icon';
|
||||
import '@material/mwc-button';
|
||||
import '@material/mwc-dialog';
|
||||
import '@material/mwc-icon';
|
||||
import { EmojiPicker } from 'emoji-picker-js';
|
||||
import { generateHTML } from '@tiptap/core'
|
||||
import StarterKit from '@tiptap/starter-kit'
|
||||
import Underline from '@tiptap/extension-underline';
|
||||
import Highlight from '@tiptap/extension-highlight'
|
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
||||
let toggledMessage = {}
|
||||
class ChatScroller extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
getNewMessage: { attribute: false },
|
||||
getOldMessage: { attribute: false },
|
||||
emojiPicker: { attribute: false },
|
||||
escapeHTML: { attribute: false },
|
||||
initialMessages: { type: Array }, // First set of messages to load.. 15 messages max ( props )
|
||||
messages: { type: Array },
|
||||
hideMessages: { type: Array }
|
||||
hideMessages: { type: Array },
|
||||
setRepliedToMessageObj: { attribute: false },
|
||||
setEditedMessageObj: { attribute: false },
|
||||
sendMessage: { attribute: false },
|
||||
sendMessageForward: { attribute: false },
|
||||
showLastMessageRefScroller: { attribute: false },
|
||||
emojiPicker: { attribute: false },
|
||||
isLoadingMessages: { type: Boolean},
|
||||
setIsLoadingMessages: { attribute: false },
|
||||
chatId: { type: String },
|
||||
setForwardProperties: { attribute: false },
|
||||
setOpenPrivateMessage: { attribute: false },
|
||||
setOpenUserInfo: { attribute: false },
|
||||
setOpenTipUser: { attribute: false },
|
||||
setUserName: { attribute: false },
|
||||
setSelectedHead: { attribute: false },
|
||||
openTipUser: { type: Boolean },
|
||||
openUserInfo: { type: Boolean },
|
||||
userName: { type: String },
|
||||
selectedHead: { type: Object },
|
||||
goToRepliedMessage: { attribute: false },
|
||||
getOldMessageAfter: {attribute: false}
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,60 +65,164 @@ class ChatScroller extends LitElement {
|
||||
this._downObserverHandler = this._downObserverHandler.bind(this)
|
||||
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address
|
||||
this.hideMessages = JSON.parse(localStorage.getItem("MessageBlockedAddresses") || "[]")
|
||||
this.openTipUser = false;
|
||||
this.openUserInfo = false;
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let formattedMessages = this.messages.reduce((messageArray, message, index) => {
|
||||
const lastGroupedMessage = messageArray[messageArray.length - 1];
|
||||
let timestamp;
|
||||
let sender;
|
||||
let repliedToData;
|
||||
|
||||
let firstMessageInChat;
|
||||
|
||||
if (index === 0) {
|
||||
firstMessageInChat = true;
|
||||
} else {
|
||||
firstMessageInChat = false;
|
||||
}
|
||||
|
||||
message = {...message, firstMessageInChat}
|
||||
|
||||
if (lastGroupedMessage) {
|
||||
timestamp = lastGroupedMessage.timestamp;
|
||||
sender = lastGroupedMessage.sender;
|
||||
repliedToData = lastGroupedMessage.repliedToData;
|
||||
}
|
||||
const isSameGroup = Math.abs(timestamp - message.timestamp) < 600000 && sender === message.sender && !repliedToData;
|
||||
|
||||
if (isSameGroup) {
|
||||
messageArray[messageArray.length - 1].messages = [...(messageArray[messageArray.length - 1]?.messages || []), message];
|
||||
} else {
|
||||
messageArray.push({
|
||||
messages: [message],
|
||||
...message
|
||||
});
|
||||
}
|
||||
return messageArray;
|
||||
}, [])
|
||||
|
||||
|
||||
return html`
|
||||
${this.isLoadingMessages ? html`
|
||||
<div class="spinnerContainer">
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
</div>
|
||||
` : ''}
|
||||
<ul id="viewElement" class="chat-list clearfix">
|
||||
<div id="upObserver"></div>
|
||||
${repeat(
|
||||
this.messages,
|
||||
${formattedMessages.map((formattedMessage) => {
|
||||
return repeat(
|
||||
formattedMessage.messages,
|
||||
(message) => message.reference,
|
||||
(message) => html`<message-template .emojiPicker=${this.emojiPicker} .escapeHTML=${this.escapeHTML} .messageObj=${message} .hideMessages=${this.hideMessages}></message-template>`
|
||||
)}
|
||||
(message, indexMessage) => html`
|
||||
<message-template
|
||||
.emojiPicker=${this.emojiPicker}
|
||||
.escapeHTML=${this.escapeHTML}
|
||||
.messageObj=${message}
|
||||
.hideMessages=${this.hideMessages}
|
||||
.setRepliedToMessageObj=${this.setRepliedToMessageObj}
|
||||
.setEditedMessageObj=${this.setEditedMessageObj}
|
||||
.sendMessage=${this.sendMessage}
|
||||
.sendMessageForward=${this.sendMessageForward}
|
||||
?isFirstMessage=${indexMessage === 0}
|
||||
?isSingleMessageInGroup=${formattedMessage.messages.length > 1}
|
||||
?isLastMessageInGroup=${indexMessage === formattedMessage.messages.length - 1}
|
||||
.setToggledMessage=${this.setToggledMessage}
|
||||
.setForwardProperties=${this.setForwardProperties}
|
||||
.setOpenPrivateMessage=${(val) => this.setOpenPrivateMessage(val)}
|
||||
.setOpenTipUser=${(val) => this.setOpenTipUser(val)}
|
||||
.setOpenUserInfo=${(val) => this.setOpenUserInfo(val)}
|
||||
.setUserName=${(val) => this.setUserName(val)}
|
||||
id=${message.reference}
|
||||
.goToRepliedMessage=${this.goToRepliedMessage}
|
||||
>
|
||||
</message-template>`
|
||||
)
|
||||
})}
|
||||
<div id='downObserver'></div>
|
||||
<div class='last-message-ref'>
|
||||
<vaadin-icon icon='vaadin:arrow-circle-down' slot='icon' @click=${() => {
|
||||
this.shadowRoot.getElementById('downObserver').scrollIntoView({
|
||||
behavior: 'smooth',
|
||||
})
|
||||
}}>
|
||||
</vaadin-icon>
|
||||
</div>
|
||||
</ul>
|
||||
`
|
||||
}
|
||||
|
||||
shouldUpdate(changedProperties) {
|
||||
if(changedProperties.has('isLoadingMessages')){
|
||||
return true
|
||||
}
|
||||
if(changedProperties.has('chatId') && changedProperties.get('chatId')){
|
||||
return true
|
||||
}
|
||||
if(changedProperties.has('openTipUser')){
|
||||
return true
|
||||
}
|
||||
if(changedProperties.has('openUserInfo')){
|
||||
return true
|
||||
}
|
||||
if(changedProperties.has('userName')){
|
||||
return true
|
||||
}
|
||||
// Only update element if prop1 changed.
|
||||
return changedProperties.has('messages');
|
||||
}
|
||||
|
||||
async getUpdateComplete() {
|
||||
await super.getUpdateComplete();
|
||||
const marginElements = Array.from(this.shadowRoot.querySelectorAll('message-template'));
|
||||
await Promise.all(marginElements.map(el => el.updateComplete));
|
||||
return true;
|
||||
}
|
||||
|
||||
setToggledMessage(message) {
|
||||
toggledMessage = message;
|
||||
}
|
||||
|
||||
async firstUpdated() {
|
||||
this.viewElement = this.shadowRoot.getElementById('viewElement')
|
||||
this.upObserverElement = this.shadowRoot.getElementById('upObserver')
|
||||
this.downObserverElement = this.shadowRoot.getElementById('downObserver')
|
||||
|
||||
this.emojiPicker.on('emoji', selection => {
|
||||
|
||||
this.sendMessage({
|
||||
type: 'reaction',
|
||||
editedMessageObj: toggledMessage,
|
||||
reaction: selection.emoji,
|
||||
})
|
||||
});
|
||||
this.viewElement = this.shadowRoot.getElementById('viewElement');
|
||||
this.upObserverElement = this.shadowRoot.getElementById('upObserver');
|
||||
this.downObserverElement = this.shadowRoot.getElementById('downObserver');
|
||||
// Intialize Observers
|
||||
this.upElementObserver()
|
||||
this.downElementObserver()
|
||||
await this.updateComplete
|
||||
this.viewElement.scrollTop = this.viewElement.scrollHeight + 50
|
||||
this.upElementObserver();
|
||||
this.downElementObserver();
|
||||
await this.getUpdateComplete();
|
||||
this.viewElement.scrollTop = this.viewElement.scrollHeight + 50;
|
||||
}
|
||||
|
||||
_getOldMessage(_scrollElement) {
|
||||
this.getOldMessage(_scrollElement)
|
||||
}
|
||||
|
||||
_getOldMessageAfter(_scrollElement) {
|
||||
this.getOldMessageAfter(_scrollElement)
|
||||
}
|
||||
|
||||
_upObserverhandler(entries) {
|
||||
if (entries[0].isIntersecting) {
|
||||
let _scrollElement = entries[0].target.nextElementSibling
|
||||
this._getOldMessage(_scrollElement)
|
||||
if(this.messages.length < 20){
|
||||
return
|
||||
}
|
||||
this.setIsLoadingMessages(true);
|
||||
let _scrollElement = entries[0].target.nextElementSibling;
|
||||
this._getOldMessage(_scrollElement);
|
||||
}
|
||||
}
|
||||
|
||||
_downObserverHandler(entries) {
|
||||
if (!entries[0].isIntersecting) {
|
||||
this.shadowRoot.querySelector(".last-message-ref").style.opacity = '1'
|
||||
let _scrollElement = entries[0].target.previousElementSibling;
|
||||
// this._getOldMessageAfter(_scrollElement);
|
||||
this.showLastMessageRefScroller(true);
|
||||
} else {
|
||||
this.shadowRoot.querySelector(".last-message-ref").style.opacity = '0'
|
||||
this.showLastMessageRefScroller(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,9 +232,8 @@ class ChatScroller extends LitElement {
|
||||
rootMargin: '0px',
|
||||
threshold: 1
|
||||
};
|
||||
|
||||
const observer = new IntersectionObserver(this._upObserverhandler, options)
|
||||
observer.observe(this.upObserverElement)
|
||||
const observer = new IntersectionObserver(this._upObserverhandler, options);
|
||||
observer.observe(this.upObserverElement);
|
||||
}
|
||||
|
||||
downElementObserver() {
|
||||
@ -111,17 +242,13 @@ class ChatScroller extends LitElement {
|
||||
rootMargin: '0px',
|
||||
threshold: 1
|
||||
}
|
||||
|
||||
// identify an element to observe
|
||||
const elementToObserve = this.downObserverElement
|
||||
|
||||
const elementToObserve = this.downObserverElement;
|
||||
// passing it a callback function
|
||||
const observer = new IntersectionObserver(this._downObserverHandler, options)
|
||||
|
||||
const observer = new IntersectionObserver(this._downObserverHandler, options);
|
||||
// call `observe()` on that MutationObserver instance,
|
||||
// passing it the element to observe, and the options object
|
||||
observer.observe(elementToObserve)
|
||||
|
||||
observer.observe(elementToObserve);
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,7 +264,26 @@ class MessageTemplate extends LitElement {
|
||||
hideMessages: { type: Array },
|
||||
openDialogPrivateMessage: { type: Boolean },
|
||||
openDialogBlockUser: { type: Boolean },
|
||||
showBlockAddressIcon: { type: Boolean }
|
||||
showBlockAddressIcon: { type: Boolean },
|
||||
setRepliedToMessageObj: { attribute: false },
|
||||
setEditedMessageObj: { attribute: false },
|
||||
sendMessage: { attribute: false },
|
||||
sendMessageForward: { attribute: false },
|
||||
openDialogImage: { attribute: false },
|
||||
openDeleteImage: { type: Boolean },
|
||||
isImageLoaded: { type: Boolean },
|
||||
isFirstMessage: { type: Boolean },
|
||||
isSingleMessageInGroup: { type: Boolean },
|
||||
isLastMessageInGroup: { type: Boolean },
|
||||
setToggledMessage: { attribute: false },
|
||||
setForwardProperties: { attribute: false },
|
||||
viewImage: { type: Boolean },
|
||||
setOpenPrivateMessage : { attribute: false },
|
||||
setOpenTipUser: { attribute: false },
|
||||
setOpenUserInfo: { attribute: false },
|
||||
setUserName: { attribute: false },
|
||||
openTipUser:{ type: Boolean },
|
||||
goToRepliedMessage: { attribute: false },
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,6 +294,13 @@ class MessageTemplate extends LitElement {
|
||||
this.openDialogBlockUser = false
|
||||
this.showBlockAddressIcon = false
|
||||
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address
|
||||
this.imageFetches = 0
|
||||
this.openDialogImage = false
|
||||
this.isImageLoaded = false
|
||||
this.isFirstMessage = false
|
||||
this.isSingleMessageInGroup = false
|
||||
this.isLastMessageInGroup = false
|
||||
this.viewImage = false
|
||||
}
|
||||
|
||||
static styles = [chatStyles]
|
||||
@ -171,7 +324,6 @@ class MessageTemplate extends LitElement {
|
||||
}
|
||||
|
||||
showBlockIconFunc(bool) {
|
||||
this.shadowRoot.querySelector(".chat-hover").focus({ preventScroll: true })
|
||||
if (bool) {
|
||||
this.showBlockAddressIcon = true;
|
||||
} else {
|
||||
@ -180,51 +332,343 @@ class MessageTemplate extends LitElement {
|
||||
}
|
||||
|
||||
render() {
|
||||
const hidemsg = this.hideMessages
|
||||
const hidemsg = this.hideMessages;
|
||||
let message = "";
|
||||
let messageVersion2 = ""
|
||||
let reactions = [];
|
||||
let repliedToData = null;
|
||||
let image = null;
|
||||
let isImageDeleted = false;
|
||||
let version = 0;
|
||||
let isForwarded = false
|
||||
let isEdited = false
|
||||
try {
|
||||
const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage);
|
||||
if(parsedMessageObj.version.toString() === '2'){
|
||||
|
||||
let avatarImg = ''
|
||||
let nameMenu = ''
|
||||
let levelFounder = ''
|
||||
let hideit = hidemsg.includes(this.messageObj.sender)
|
||||
|
||||
levelFounder = html`<level-founder checkleveladdress="${this.messageObj.sender}"></level-founder>`
|
||||
messageVersion2 = generateHTML(parsedMessageObj.messageText, [
|
||||
StarterKit,
|
||||
Underline,
|
||||
Highlight
|
||||
// other extensions …
|
||||
])
|
||||
}
|
||||
message = parsedMessageObj.messageText;
|
||||
repliedToData = this.messageObj.repliedToData;
|
||||
isImageDeleted = parsedMessageObj.isImageDeleted;
|
||||
reactions = parsedMessageObj.reactions || [];
|
||||
version = parsedMessageObj.version
|
||||
isForwarded = parsedMessageObj.type === 'forward'
|
||||
isEdited = this.messageObj.editedTimestamp && true
|
||||
if (parsedMessageObj.images && Array.isArray(parsedMessageObj.images) && parsedMessageObj.images.length > 0) {
|
||||
image = parsedMessageObj.images[0];
|
||||
}
|
||||
} catch (error) {
|
||||
message = this.messageObj.decodedMessage;
|
||||
}
|
||||
let avatarImg = '';
|
||||
let imageHTML = '';
|
||||
let imageHTMLDialog = '';
|
||||
let imageUrl = '';
|
||||
let nameMenu = '';
|
||||
let levelFounder = '';
|
||||
let hideit = hidemsg.includes(this.messageObj.sender);
|
||||
let forwarded = ''
|
||||
let edited = ''
|
||||
|
||||
levelFounder = html`<level-founder checkleveladdress="${this.messageObj.sender}"></level-founder>`;
|
||||
if (this.messageObj.senderName) {
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${this.messageObj.senderName}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`
|
||||
avatarImg = html`<img src="${avatarUrl}" style="max-width:100%; max-height:100%;" onerror="this.onerror=null; this.src='/img/incognito.png';" />`
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
|
||||
const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${this.messageObj.senderName}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`;
|
||||
avatarImg = html`<img src="${avatarUrl}" style="max-width:100%; max-height:100%;" onerror="this.onerror=null; this.src='/img/qortal-chat-logo.png';" />`;
|
||||
} else {
|
||||
avatarImg = html`<img src='/img/qortal-chat-logo.png' style="max-width:100%; max-height:100%;" onerror="this.onerror=null;" />`
|
||||
}
|
||||
|
||||
if (this.messageObj.sender === this.myAddress) {
|
||||
nameMenu = html`<span style="color: #03a9f4;">${this.messageObj.senderName ? this.messageObj.senderName : this.messageObj.sender}</span>`
|
||||
} else {
|
||||
nameMenu = html`<span>${this.messageObj.senderName ? this.messageObj.senderName : this.messageObj.sender}</span>`
|
||||
const createImage = (imageUrl) => {
|
||||
const imageHTMLRes = new Image();
|
||||
imageHTMLRes.src = imageUrl;
|
||||
imageHTMLRes.style= "max-width:45vh; max-height:40vh; border-radius: 5px; cursor: pointer";
|
||||
imageHTMLRes.onclick= () => {
|
||||
this.openDialogImage = true;
|
||||
}
|
||||
imageHTMLRes.onload = () => {
|
||||
this.isImageLoaded = true;
|
||||
}
|
||||
imageHTMLRes.onerror = () => {
|
||||
if (this.imageFetches < 4) {
|
||||
setTimeout(() => {
|
||||
this.imageFetches = this.imageFetches + 1;
|
||||
imageHTMLRes.src = imageUrl;
|
||||
}, 500);
|
||||
} else {
|
||||
imageHTMLRes.src = '/img/chain.png';
|
||||
imageHTMLRes.style= "max-width:45vh; max-height:20vh; border-radius: 5px; filter: opacity(0.5)";
|
||||
imageHTMLRes.onclick= () => {
|
||||
|
||||
}
|
||||
|
||||
this.isImageLoaded = true
|
||||
}
|
||||
};
|
||||
return imageHTMLRes;
|
||||
}
|
||||
if (image) {
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
|
||||
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){
|
||||
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`
|
||||
<span class="${this.messageObj.sender === this.myAddress && 'message-data-my-name'}">
|
||||
${this.messageObj.senderName ? this.messageObj.senderName : cropAddress(this.messageObj.sender)}
|
||||
</span>
|
||||
`;
|
||||
forwarded = html`
|
||||
<span class="${this.messageObj.sender === this.myAddress && 'message-data-forward'}">
|
||||
${translate("blockpage.bcchange17")}
|
||||
</span>
|
||||
`;
|
||||
|
||||
edited = html`
|
||||
<span class="edited-message-style">
|
||||
${translate("chatpage.cchange68")}
|
||||
</span>
|
||||
`;
|
||||
|
||||
if (repliedToData) {
|
||||
try {
|
||||
const parsedMsg = JSON.parse(repliedToData.decodedMessage);
|
||||
repliedToData.decodedMessage = parsedMsg;
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
}
|
||||
const escapedMessage = this.escapeHTML(message)
|
||||
const replacedMessage = escapedMessage.replace(new RegExp('\r?\n','g'), '<br />');
|
||||
|
||||
return hideit ? html`<li class="clearfix"></li>` : html`
|
||||
<li class="clearfix message-parent">
|
||||
<div class="message-data ${this.messageObj.sender === this.myAddress ? "" : ""}">
|
||||
<span class="message-data-name">${nameMenu}</span>
|
||||
<span class="message-data-level">${levelFounder}</span>
|
||||
<span class="message-data-time"><message-time timestamp=${this.messageObj.timestamp}></message-time></span>
|
||||
<li
|
||||
class="clearfix message-parent"
|
||||
style="${(this.isSingleMessageInGroup === true && this.isLastMessageInGroup === false && reactions.length === 0) ?
|
||||
'padding-bottom: 0;'
|
||||
: null}
|
||||
${this.isFirstMessage && 'margin-top: 20px;'}">
|
||||
<div>
|
||||
<div
|
||||
class="message-container"
|
||||
style="${(this.isSingleMessageInGroup === true && this.isLastMessageInGroup === false) && 'margin-bottom: 0'}">
|
||||
<div class="message-subcontainer1">
|
||||
${(this.isSingleMessageInGroup === false ||
|
||||
(this.isSingleMessageInGroup === true && this.isLastMessageInGroup === true))
|
||||
? (
|
||||
html`
|
||||
<div
|
||||
style=${this.myAddress === this.messageObj.sender ? "cursor: auto;" : "cursor: pointer;"}
|
||||
@click=${() => {
|
||||
if (this.myAddress === this.messageObj.sender) return;
|
||||
this.setOpenUserInfo(true);
|
||||
this.setUserName(this.messageObj);
|
||||
}} class="message-data-avatar">
|
||||
${avatarImg}
|
||||
</div>
|
||||
`
|
||||
) :
|
||||
html`
|
||||
<div class="message-data-avatar"></div>
|
||||
`}
|
||||
<div
|
||||
class="${`message-subcontainer2
|
||||
${((this.isFirstMessage === true && this.isSingleMessageInGroup === false) ||
|
||||
(this.isSingleMessageInGroup === true && this.isLastMessageInGroup === true)) &&
|
||||
'message-triangle'}`}"
|
||||
style="${(this.isSingleMessageInGroup === true && this.isLastMessageInGroup === false) ? 'margin-bottom: 0;' : null}
|
||||
${(this.isFirstMessage === false && this.isSingleMessageInGroup === true && this.isLastMessageInGroup === false)
|
||||
? 'border-radius: 8px 25px 25px 8px;'
|
||||
: (this.isFirstMessage === true && this.isSingleMessageInGroup === true && this.isLastMessageInGroup === false)
|
||||
? 'border-radius: 27px 25px 25px 12px;'
|
||||
: (this.isFirstMessage === false && this.isSingleMessageInGroup === true && this.isLastMessageInGroup === true) ?
|
||||
'border-radius: 10px 25px 25px 0;'
|
||||
: (this.isFirstMessage === true && this.isSingleMessageInGroup === false && this.isLastMessageInGroup === true)
|
||||
? 'border-radius: 25px 25px 25px 0px;'
|
||||
: null
|
||||
}">
|
||||
<div class="message-user-info">
|
||||
${this.isFirstMessage ?
|
||||
html`
|
||||
<span
|
||||
style=${this.myAddress === this.messageObj.sender ? "cursor: auto;" : "cursor: pointer;"}
|
||||
@click=${() => {
|
||||
if (this.myAddress === this.messageObj.sender) return;
|
||||
this.setOpenUserInfo(true);
|
||||
this.setUserName(this.messageObj);
|
||||
}}
|
||||
class="message-data-name">
|
||||
${nameMenu}
|
||||
</span>
|
||||
`
|
||||
: null
|
||||
}
|
||||
${isForwarded ?
|
||||
html`
|
||||
<span class="forwarded-text">
|
||||
${forwarded}
|
||||
</span>
|
||||
`
|
||||
: null
|
||||
}
|
||||
${this.isFirstMessage ? (
|
||||
html`
|
||||
<span class="message-data-level">${levelFounder}</span>
|
||||
`
|
||||
) : null}
|
||||
</div>
|
||||
${repliedToData && html`
|
||||
<div class="original-message"
|
||||
@click=${()=> {
|
||||
this.goToRepliedMessage(repliedToData)
|
||||
}}>
|
||||
<p
|
||||
|
||||
|
||||
class="original-message-sender">
|
||||
${repliedToData.senderName ?? cropAddress(repliedToData.sender)}
|
||||
</p>
|
||||
<p class="replied-message">
|
||||
|
||||
|
||||
${version.toString() === '1' ? html`
|
||||
${repliedToData.decodedMessage.messageText}
|
||||
` : ''}
|
||||
${version.toString() === '2' ? html`
|
||||
${unsafeHTML(generateHTML(repliedToData.decodedMessage.messageText, [
|
||||
StarterKit,
|
||||
Underline,
|
||||
Highlight
|
||||
// other extensions …
|
||||
]))}
|
||||
` : ''}
|
||||
|
||||
<!-- ${repliedToData.decodedMessage.messageText} -->
|
||||
</p>
|
||||
</div>
|
||||
`}
|
||||
${image && !isImageDeleted && !this.viewImage && this.myAddress !== this.messageObj.sender ? html`
|
||||
<div
|
||||
@click=${()=> {
|
||||
this.viewImage = true
|
||||
}}
|
||||
class=${[`image-container`, !this.isImageLoaded ? 'defaultSize' : ''].join(' ')}
|
||||
style=${this.isFirstMessage && "margin-top: 10px;"}>
|
||||
<div style="display:flex;width:100%;height:100%;justify-content:center;align-items:center;cursor:pointer;color:var(--black)">
|
||||
${translate("chatpage.cchange40")}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
` : html``}
|
||||
${image && !isImageDeleted && (this.viewImage || this.myAddress === this.messageObj.sender) ? html`
|
||||
<div
|
||||
class=${[`image-container`, !this.isImageLoaded ? 'defaultSize' : ''].join(' ')}
|
||||
style=${this.isFirstMessage && "margin-top: 10px;"}>
|
||||
${imageHTML}<vaadin-icon
|
||||
@click=${() => {
|
||||
this.openDeleteImage = true;
|
||||
this.chatE
|
||||
}}
|
||||
class="image-delete-icon" icon="vaadin:close" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
` : image && isImageDeleted ? html`
|
||||
<p class="image-deleted-msg">This image has been deleted</p>
|
||||
` : html``}
|
||||
<div
|
||||
id="messageContent"
|
||||
class="message"
|
||||
style=${(image && replacedMessage !== "") &&"margin-top: 15px;"}>
|
||||
${version.toString() === '2' ? html`
|
||||
${unsafeHTML(messageVersion2)}
|
||||
` : ''}
|
||||
${version.toString() === '1' ? html`
|
||||
${unsafeHTML(this.emojiPicker.parse(replacedMessage))}
|
||||
` : ''}
|
||||
<div
|
||||
style=${isEdited
|
||||
? "justify-content: space-between;"
|
||||
: "justify-content: flex-end;"}
|
||||
class="${((this.isFirstMessage === false &&
|
||||
this.isSingleMessageInGroup === true &&
|
||||
this.isLastMessageInGroup === true) ||
|
||||
(this.isFirstMessage === true &&
|
||||
this.isSingleMessageInGroup === false &&
|
||||
this.isLastMessageInGroup === true))
|
||||
? 'message-data-time'
|
||||
: 'message-data-time-hidden'
|
||||
}">
|
||||
${isEdited ?
|
||||
html`
|
||||
<span>
|
||||
${edited}
|
||||
</span>
|
||||
`
|
||||
: null
|
||||
}
|
||||
<message-time timestamp=${this.messageObj.timestamp}></message-time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="message-data-avatar" style="width:42px; height:42px; ${this.messageObj.sender === this.myAddress ? "float:left;" : "float:left;"} margin:3px;">${avatarImg}</div>
|
||||
<div class="message-container">
|
||||
<div id="messageContent" class="message ${this.messageObj.sender === this.myAddress ? "my-message float-left" : "other-message float-left"}">${unsafeHTML(this.emojiPicker.parse(this.escapeHTML(this.messageObj.decodedMessage)))}</div>
|
||||
<chat-menu
|
||||
tabindex="0"
|
||||
class="chat-hover"
|
||||
style=${this.showBlockAddressIcon && "display: block"}
|
||||
style="${this.showBlockAddressIcon && 'display: block;'}"
|
||||
toblockaddress="${this.messageObj.sender}"
|
||||
.showPrivateMessageModal=${() => this.showPrivateMessageModal()}
|
||||
.showBlockUserModal=${() => this.showBlockUserModal()}
|
||||
.showBlockIconFunc=${(props) => this.showBlockIconFunc(props)}
|
||||
.showBlockAddressIcon=${this.showBlockAddressIcon}
|
||||
.originalMessage=${{...this.messageObj, message}}
|
||||
.setRepliedToMessageObj=${this.setRepliedToMessageObj}
|
||||
.setEditedMessageObj=${this.setEditedMessageObj}
|
||||
.myAddress=${this.myAddress}
|
||||
@blur=${() => this.showBlockIconFunc(false)}
|
||||
.sendMessage=${this.sendMessage}
|
||||
.sendMessageForward=${this.sendMessageForward}
|
||||
version=${version}
|
||||
.emojiPicker=${this.emojiPicker}
|
||||
.setToggledMessage=${this.setToggledMessage}
|
||||
.setForwardProperties=${this.setForwardProperties}
|
||||
?firstMessageInChat=${this.messageObj.firstMessageInChat}
|
||||
.setOpenPrivateMessage=${(val) => this.setOpenPrivateMessage(val)}
|
||||
.setOpenTipUser=${(val) => this.setOpenTipUser(val)}
|
||||
.setUserName=${(val) => this.setUserName(val)}
|
||||
>
|
||||
</chat-menu>
|
||||
</div>
|
||||
<div class="message-reactions" style="${reactions.length > 0 &&
|
||||
'margin-top: 10px; margin-bottom: 5px;'}">
|
||||
${reactions.map((reaction)=> {
|
||||
return html`
|
||||
<span
|
||||
@click=${() => this.sendMessage({
|
||||
type: 'reaction',
|
||||
editedMessageObj: this.messageObj,
|
||||
reaction: reaction.type,
|
||||
})}
|
||||
class="reactions-bg">
|
||||
${reaction.type} ${reaction.qty}
|
||||
</span>`
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<chat-modals
|
||||
.openDialogPrivateMessage=${this.openDialogPrivateMessage}
|
||||
@ -235,6 +679,53 @@ class MessageTemplate extends LitElement {
|
||||
toblockaddress=${this.messageObj.sender}
|
||||
>
|
||||
</chat-modals>
|
||||
<mwc-dialog
|
||||
id="showDialogPublicKey"
|
||||
?open=${this.openDialogImage}
|
||||
@closed=${()=> {
|
||||
this.openDialogImage = false
|
||||
}}>
|
||||
<div class="dialog-header"></div>
|
||||
<div class="dialog-container imageContainer">
|
||||
${imageHTMLDialog}
|
||||
</div>
|
||||
<mwc-button
|
||||
slot="primaryAction"
|
||||
dialogAction="cancel"
|
||||
class="red"
|
||||
@click=${()=>{
|
||||
|
||||
this.openDialogImage = false
|
||||
}}
|
||||
>
|
||||
${translate("general.close")}
|
||||
</mwc-button>
|
||||
</mwc-dialog>
|
||||
<mwc-dialog
|
||||
hideActions
|
||||
?open=${this.openDeleteImage}
|
||||
@closed=${()=> {
|
||||
this.openDeleteImage = false;
|
||||
}}>
|
||||
<div class="delete-image-msg">
|
||||
<p>Are you sure you want to delete this image?</p>
|
||||
</div>
|
||||
<div class="modal-button-row" @click=${() => this.openDeleteImage = false}>
|
||||
<button class="modal-button-red">
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
class="modal-button"
|
||||
@click=${() => this.sendMessage({
|
||||
type: 'delete',
|
||||
name: image.name,
|
||||
identifier: image.identifier,
|
||||
editedMessageObj: this.messageObj,
|
||||
})}>
|
||||
Yes
|
||||
</button>
|
||||
</div>
|
||||
</mwc-dialog>
|
||||
`
|
||||
}
|
||||
}
|
||||
@ -245,18 +736,30 @@ class ChatMenu extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
menuItems: { type: Array },
|
||||
selectedAddress: { type: Object },
|
||||
showPrivateMessageModal: {type: Function},
|
||||
showBlockUserModal: {type: Function},
|
||||
showPrivateMessageModal: {attribute: false},
|
||||
showBlockUserModal: {attribute: false},
|
||||
toblockaddress: { type: String, attribute: true },
|
||||
showBlockIconFunc: {type: Function},
|
||||
showBlockAddressIcon: {type: Boolean}
|
||||
showBlockIconFunc: {attribute: false},
|
||||
showBlockAddressIcon: { type: Boolean },
|
||||
originalMessage: { type: Object },
|
||||
setRepliedToMessageObj: {attribute: false},
|
||||
setEditedMessageObj: {attribute: false},
|
||||
myAddress: { type: Object },
|
||||
emojiPicker: { attribute: false },
|
||||
sendMessage: { attribute: false },
|
||||
version: { type: String },
|
||||
setToggledMessage: { attribute: false },
|
||||
sendMessageForward: { attribute: false },
|
||||
setForwardProperties: { attribute: false },
|
||||
firstMessageInChat: { type: Boolean },
|
||||
setOpenPrivateMessage: { attribute: false },
|
||||
setOpenTipUser: { attribute: false },
|
||||
setUserName: { attribute: false },
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.selectedAddress = window.parent.reduxStore.getState().app.selectedAddress.address;
|
||||
this.showPrivateMessageModal = () => {};
|
||||
this.showBlockUserModal = () => {};
|
||||
}
|
||||
@ -276,22 +779,148 @@ class ChatMenu extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
versionErrorSnack(){
|
||||
let errorMsg = get("chatpage.cchange34")
|
||||
parentEpml.request('showSnackBar', `${errorMsg}`)
|
||||
}
|
||||
|
||||
async messageForwardFunc(){
|
||||
let parsedMessageObj = {}
|
||||
let publicKey = {
|
||||
hasPubKey: false,
|
||||
key: ''
|
||||
}
|
||||
try {
|
||||
parsedMessageObj = JSON.parse(this.originalMessage.decodedMessage);
|
||||
|
||||
} catch (error) {
|
||||
parsedMessageObj = {}
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await parentEpml.request('apiCall', {
|
||||
type: 'api',
|
||||
url: `/addresses/publickey/${this._chatId}`
|
||||
})
|
||||
if (res.error === 102) {
|
||||
publicKey.key = ''
|
||||
publicKey.hasPubKey = false
|
||||
} else if (res !== false) {
|
||||
publicKey.key = res
|
||||
publicKey.hasPubKey = true
|
||||
} else {
|
||||
publicKey.key = ''
|
||||
publicKey.hasPubKey = false
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
const message = {
|
||||
...parsedMessageObj,
|
||||
type: 'forward'
|
||||
}
|
||||
const stringifyMessageObject = JSON.stringify(message)
|
||||
this.setForwardProperties(stringifyMessageObject)
|
||||
|
||||
} catch (error) {
|
||||
console.log({error})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="container" style=${this.showBlockAddressIcon && "width: 70px" }>
|
||||
<div class="menu-icon tooltip" data-text="${translate("blockpage.bcchange9")}" @click="${() => this.showPrivateMessageModal()}">
|
||||
<div class="container">
|
||||
<div
|
||||
class=${`menu-icon reaction ${!this.firstMessageInChat ? "tooltip" : ""}`}
|
||||
data-text="${translate("blockpage.bcchange13")}"
|
||||
@click=${(e) => {
|
||||
if(this.version === '0'){
|
||||
this.versionErrorSnack()
|
||||
return
|
||||
}
|
||||
try {
|
||||
this.setToggledMessage(this.originalMessage)
|
||||
this.emojiPicker.togglePicker(e.target)
|
||||
} catch (error) {
|
||||
console.log({error})
|
||||
}
|
||||
|
||||
}}
|
||||
>
|
||||
<vaadin-icon icon="vaadin:smiley-o" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
<div
|
||||
class=${`menu-icon ${!this.firstMessageInChat ? "tooltip" : ""}`}
|
||||
data-text="${translate("blockpage.bcchange14")}"
|
||||
@click="${() => {
|
||||
this.messageForwardFunc()
|
||||
}}">
|
||||
<vaadin-icon icon="vaadin:arrow-forward" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
<div
|
||||
class=${`menu-icon ${!this.firstMessageInChat ? "tooltip" : ""}`}
|
||||
data-text="${translate("blockpage.bcchange9")}"
|
||||
@click="${() => this.setOpenPrivateMessage({
|
||||
name: this.originalMessage.senderName ? this.originalMessage.senderName : this.originalMessage.sender,
|
||||
open: true
|
||||
})}">
|
||||
<vaadin-icon icon="vaadin:paperplane" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
<div class="menu-icon tooltip" data-text="${translate("blockpage.bcchange8")}" @click="${() => this.copyToClipboard(this.toblockaddress)}">
|
||||
<div class=${`menu-icon ${!this.firstMessageInChat ? "tooltip" : ""}`} data-text="${translate("blockpage.bcchange8")}" @click="${() => this.copyToClipboard(this.toblockaddress)}">
|
||||
<vaadin-icon icon="vaadin:copy" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
<div class="menu-icon tooltip" data-text="${translate("blockpage.bcchange10")}" @click="${() => this.showBlockIconFunc(true)}">
|
||||
<div
|
||||
class=${`menu-icon ${!this.firstMessageInChat ? "tooltip" : ""}`}
|
||||
data-text="${translate("blockpage.bcchange11")}"
|
||||
@click="${() => {
|
||||
if (this.version === '0') {
|
||||
this.versionErrorSnack()
|
||||
return
|
||||
}
|
||||
this.setRepliedToMessageObj({...this.originalMessage, version: this.version});
|
||||
}}">
|
||||
<vaadin-icon icon="vaadin:reply" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
|
||||
${this.myAddress === this.originalMessage.sender ? (
|
||||
html`
|
||||
<div
|
||||
class=${`menu-icon ${!this.firstMessageInChat ? "tooltip" : ""}`}
|
||||
data-text="${translate("blockpage.bcchange12")}"
|
||||
@click=${() => {
|
||||
if(this.version === '0'){
|
||||
this.versionErrorSnack()
|
||||
return
|
||||
}
|
||||
this.setEditedMessageObj(this.originalMessage);
|
||||
}}>
|
||||
<vaadin-icon icon="vaadin:pencil" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
`
|
||||
) : html`<div></div>`}
|
||||
${this.myAddress !== this.originalMessage.sender ? (
|
||||
html`
|
||||
<div
|
||||
class=${`menu-icon ${!this.firstMessageInChat ? "tooltip" : ""}`}
|
||||
data-text="${translate("blockpage.bcchange18")}"
|
||||
@click=${(e) => {
|
||||
e.preventDefault();
|
||||
this.setUserName(this.originalMessage);
|
||||
this.setOpenTipUser(true);
|
||||
}}>
|
||||
<vaadin-icon icon="vaadin:dollar" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
`
|
||||
) : html`<div></div>`}
|
||||
<div class=${`menu-icon ${!this.firstMessageInChat ? "tooltip" : ""}`} data-text="${translate("blockpage.bcchange10")}" @click="${() => this.showBlockIconFunc(true)}">
|
||||
<vaadin-icon icon="vaadin:ellipsis-dots-h" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
${this.showBlockAddressIcon
|
||||
? html`
|
||||
<div class="block-user-container">
|
||||
<div class="menu-icon block-user" @click="${() => this.showBlockUserModal()}">
|
||||
<div class="block-user" @click="${() => this.showBlockUserModal()}">
|
||||
<p>${translate("blockpage.bcchange1")}</p>
|
||||
<vaadin-icon icon="vaadin:close-circle" slot="icon"></vaadin-icon>
|
||||
</div>
|
||||
|
@ -0,0 +1,66 @@
|
||||
import { LitElement, html } from 'lit';
|
||||
import { render } from 'lit/html.js';
|
||||
import { chatSearchResultsStyles } from './ChatSearchResults-css.js'
|
||||
import { translate } from 'lit-translate';
|
||||
export class ChatSearchResults extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
onClickFunc: { attribute: false },
|
||||
closeFunc: { attribute: false },
|
||||
searchResults: { type: Array },
|
||||
isOpen: { type: Boolean },
|
||||
loading: { type: Boolean }
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [chatSearchResultsStyles]
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="chat-results-card" style=${this.isOpen ? "display: block;" : "display: none;"}>
|
||||
<vaadin-icon
|
||||
@click=${() => this.closeFunc()}
|
||||
icon="vaadin:close-small"
|
||||
slot="icon"
|
||||
class="close-icon"
|
||||
>
|
||||
</vaadin-icon>
|
||||
${this.loading ? (
|
||||
html`
|
||||
<div class="spinner-container">
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
</div>
|
||||
`
|
||||
) : (
|
||||
html`
|
||||
<p class="chat-result-header">${translate("chatpage.cchange36")}</p>
|
||||
<div class="divider"></div>
|
||||
<div class="chat-result-container">
|
||||
${this.searchResults.length === 0 ? (
|
||||
html`<p class="no-results">${translate("chatpage.cchange37")}</p>`
|
||||
) : (
|
||||
html`
|
||||
${this.searchResults.map((result) => {
|
||||
return (
|
||||
html`
|
||||
<div class="chat-result-card" @click=${() => {
|
||||
this.shadowRoot.querySelector(".chat-result-card").classList.add("active");
|
||||
this.onClickFunc(result);
|
||||
}}>
|
||||
<p class="chat-result">
|
||||
${result.name}
|
||||
</p>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
)}
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('chat-search-results', ChatSearchResults);
|
@ -0,0 +1,120 @@
|
||||
import { css } from 'lit'
|
||||
|
||||
export const chatSearchResultsStyles = css`
|
||||
.chat-results-card {
|
||||
position: relative;
|
||||
padding: 25px 20px;
|
||||
box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
|
||||
width: 300px;
|
||||
min-height: 200px;
|
||||
height: auto;
|
||||
border-radius: 5px;
|
||||
background-color: var(--white);
|
||||
}
|
||||
|
||||
.chat-result-header {
|
||||
color: var(--chat-bubble-msg-color);
|
||||
font-size: 18px;
|
||||
font-family: Montserrat, sans-serif;
|
||||
text-align: center;
|
||||
margin: 0 0 10px 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1px;
|
||||
background: var(--chat-bubble-msg-color);
|
||||
margin: 0 40px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.no-results {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.3px;
|
||||
font-size: 16px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
text-align: center;
|
||||
margin: 20px 0 0 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.chat-result-container {
|
||||
height: 200px;
|
||||
overflow-y: auto;
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.chat-result-container::-webkit-scrollbar-track {
|
||||
background-color: whitesmoke;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.chat-result-container::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
border-radius: 7px;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
.chat-result-container::-webkit-scrollbar-thumb {
|
||||
background-color: rgb(180, 176, 176);
|
||||
border-radius: 7px;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.chat-result-container::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgb(148, 146, 146);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chat-result-card {
|
||||
padding: 12px;
|
||||
margin-bottom: 15px;
|
||||
margin-top: 15px;
|
||||
transition: all 0.2s ease-in-out;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.chat-result-card:active {
|
||||
background-color: #09b814;
|
||||
}
|
||||
|
||||
.chat-result-card:hover {
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
-webkit-box-shadow: rgba(132, 132, 132, 40%) 0px 0px 6px -1px;
|
||||
box-shadow: rgba(132, 132, 132, 40%) 0px 0px 6px -1px;
|
||||
}
|
||||
|
||||
.chat-result {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.3px;
|
||||
font-size: 15px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.spinner-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
font-size: 14px;
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.close-icon:hover {
|
||||
cursor: pointer;
|
||||
font-size: 15px;
|
||||
}
|
||||
`
|
229
qortal-ui-plugins/plugins/core/components/ChatSelect.js
Normal file
@ -0,0 +1,229 @@
|
||||
import { LitElement, html, css } from 'lit'
|
||||
import { Epml } from '../../../epml.js'
|
||||
|
||||
import '@material/mwc-icon'
|
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
||||
|
||||
class ChatSelect extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
selectedAddress: { type: Object },
|
||||
config: { type: Object },
|
||||
chatInfo: { type: Object },
|
||||
iconName: { type: String },
|
||||
activeChatHeadUrl: { type: String },
|
||||
isImageLoaded: { type: Boolean },
|
||||
setActiveChatHeadUrl: {attribute: false}
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
li {
|
||||
padding: 10px 2px 20px 5px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
li:hover {
|
||||
background-color: var(--menuhover);
|
||||
}
|
||||
|
||||
.active {
|
||||
background: var(--menuactive);
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
|
||||
.img-icon {
|
||||
font-size:40px;
|
||||
color: var(--chat-group);
|
||||
}
|
||||
|
||||
.about {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.about {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.status {
|
||||
color: #92959e;
|
||||
}
|
||||
|
||||
.name {
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.selectedAddress = {}
|
||||
this.config = {
|
||||
user: {
|
||||
node: {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
this.chatInfo = {}
|
||||
this.iconName = ''
|
||||
this.activeChatHeadUrl = ''
|
||||
this.isImageLoaded = false
|
||||
this.imageFetches = 0
|
||||
}
|
||||
|
||||
createImage(imageUrl) {
|
||||
const imageHTMLRes = new Image();
|
||||
imageHTMLRes.src = imageUrl;
|
||||
imageHTMLRes.style= "width:40px; height:40px; float: left; border-radius:50%";
|
||||
imageHTMLRes.onclick= () => {
|
||||
this.openDialogImage = true;
|
||||
}
|
||||
imageHTMLRes.onload = () => {
|
||||
this.isImageLoaded = true;
|
||||
}
|
||||
imageHTMLRes.onerror = () => {
|
||||
if (this.imageFetches < 4) {
|
||||
setTimeout(() => {
|
||||
this.imageFetches = this.imageFetches + 1;
|
||||
imageHTMLRes.src = imageUrl;
|
||||
}, 500);
|
||||
} else {
|
||||
|
||||
|
||||
this.isImageLoaded = false
|
||||
}
|
||||
};
|
||||
return imageHTMLRes;
|
||||
}
|
||||
|
||||
render() {
|
||||
let avatarImg = '';
|
||||
let backupAvatarImg = ''
|
||||
if(this.chatInfo.name){
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
|
||||
const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${this.chatInfo.name}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`;
|
||||
avatarImg= this.createImage(avatarUrl)
|
||||
|
||||
}
|
||||
|
||||
return html`
|
||||
<li
|
||||
@click=${() => this.getUrl(this.chatInfo.url)}
|
||||
class="clearfix ${this.activeChatHeadUrl === this.chatInfo.url ? 'active' : ''}">
|
||||
${this.isImageLoaded ? html`${avatarImg}` : html``}
|
||||
${!this.isImageLoaded && !this.chatInfo.name && !this.chatInfo.groupName ? html`<mwc-icon class="img-icon">account_circle</mwc-icon>` :
|
||||
html``
|
||||
}
|
||||
${!this.isImageLoaded && this.chatInfo.name ?
|
||||
html`
|
||||
<div
|
||||
style="width:40px; height:40px; float: left; border-radius:50%; background: ${this.activeChatHeadUrl === this.chatInfo.url ?
|
||||
'var(--chatHeadBgActive)' :
|
||||
'var(--chatHeadBg)' };
|
||||
color: ${this.activeChatHeadUrl === this.chatInfo.url ?
|
||||
'var(--chatHeadTextActive)' :
|
||||
'var(--chatHeadText)'};
|
||||
font-weight:bold;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-transform: capitalize">
|
||||
${this.chatInfo.name.charAt(0)}
|
||||
</div>`:
|
||||
''}
|
||||
${!this.isImageLoaded && this.chatInfo.groupName ?
|
||||
html`
|
||||
<div
|
||||
style="width:40px;
|
||||
height:40px;
|
||||
float: left;
|
||||
border-radius:50%;
|
||||
background: ${this.activeChatHeadUrl === this.chatInfo.url ?
|
||||
'var(--chatHeadBgActive)' :
|
||||
'var(--chatHeadBg)' };
|
||||
color: ${this.activeChatHeadUrl === this.chatInfo.url ?
|
||||
'var(--chatHeadTextActive)' :
|
||||
'var(--chatHeadText)' };
|
||||
font-weight:bold;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-transform: capitalize">
|
||||
${this.chatInfo.groupName.charAt(0)}
|
||||
</div>`:
|
||||
''}
|
||||
<div class="about">
|
||||
<div class="name">
|
||||
<span style="float:left; padding-left: 8px; color: var(--chat-group);">
|
||||
${this.chatInfo.groupName ?
|
||||
this.chatInfo.groupName :
|
||||
this.chatInfo.name !== undefined ? this.chatInfo.name :
|
||||
this.chatInfo.address.substr(0, 15)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
let configLoaded = false
|
||||
parentEpml.ready().then(() => {
|
||||
parentEpml.subscribe('selected_address', async selectedAddress => {
|
||||
this.selectedAddress = {}
|
||||
selectedAddress = JSON.parse(selectedAddress)
|
||||
if (!selectedAddress || Object.entries(selectedAddress).length === 0) return
|
||||
this.selectedAddress = selectedAddress
|
||||
})
|
||||
parentEpml.subscribe('config', c => {
|
||||
if (!configLoaded) {
|
||||
configLoaded = true
|
||||
}
|
||||
this.config = JSON.parse(c)
|
||||
})
|
||||
})
|
||||
parentEpml.imReady()
|
||||
|
||||
|
||||
}
|
||||
|
||||
shouldUpdate(changedProperties) {
|
||||
if(changedProperties.has('activeChatHeadUrl')){
|
||||
return true
|
||||
}
|
||||
if(changedProperties.has('chatInfo')){
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
getUrl(chatUrl) {
|
||||
this.setActiveChatHeadUrl(chatUrl)
|
||||
}
|
||||
|
||||
onPageNavigation(pageUrl) {
|
||||
parentEpml.request('setPageUrl', pageUrl)
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('chat-select', ChatSelect)
|
205
qortal-ui-plugins/plugins/core/components/ChatSideNavHeads.js
Normal file
@ -0,0 +1,205 @@
|
||||
import { LitElement, html, css } from 'lit'
|
||||
import { Epml } from '../../../epml.js'
|
||||
|
||||
import '@material/mwc-icon'
|
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
||||
|
||||
class ChatSideNavHeads extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
selectedAddress: { type: Object },
|
||||
config: { type: Object },
|
||||
chatInfo: { type: Object },
|
||||
iconName: { type: String },
|
||||
activeChatHeadUrl: { type: String },
|
||||
isImageLoaded: { type: Boolean },
|
||||
setActiveChatHeadUrl: {attribute: false}
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
ul {
|
||||
list-style-type: none;
|
||||
}
|
||||
li {
|
||||
padding: 10px 2px 10px 5px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
font-size: 14px;
|
||||
transition: 0.2s background-color;
|
||||
}
|
||||
|
||||
li:hover {
|
||||
background-color: var(--lightChatHeadHover);
|
||||
}
|
||||
|
||||
.active {
|
||||
background: var(--menuactive);
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
|
||||
.img-icon {
|
||||
font-size:40px;
|
||||
color: var(--chat-group);
|
||||
}
|
||||
|
||||
.status {
|
||||
color: #92959e;
|
||||
}
|
||||
|
||||
.clearfix {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.selectedAddress = {}
|
||||
this.config = {
|
||||
user: {
|
||||
node: {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
this.chatInfo = {}
|
||||
this.iconName = ''
|
||||
this.activeChatHeadUrl = ''
|
||||
this.isImageLoaded = false
|
||||
this.imageFetches = 0
|
||||
}
|
||||
|
||||
createImage(imageUrl) {
|
||||
const imageHTMLRes = new Image();
|
||||
imageHTMLRes.src = imageUrl;
|
||||
imageHTMLRes.style= "width:30px; height:30px; float: left; border-radius:50%; font-size:14px";
|
||||
imageHTMLRes.onclick= () => {
|
||||
this.openDialogImage = true;
|
||||
}
|
||||
imageHTMLRes.onload = () => {
|
||||
this.isImageLoaded = true;
|
||||
}
|
||||
imageHTMLRes.onerror = () => {
|
||||
if (this.imageFetches < 4) {
|
||||
setTimeout(() => {
|
||||
this.imageFetches = this.imageFetches + 1;
|
||||
imageHTMLRes.src = imageUrl;
|
||||
}, 500);
|
||||
} else {
|
||||
this.isImageLoaded = false
|
||||
}
|
||||
};
|
||||
return imageHTMLRes;
|
||||
}
|
||||
|
||||
render() {
|
||||
let avatarImg = ""
|
||||
if (this.chatInfo.name) {
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
|
||||
const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${this.chatInfo.name}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`;
|
||||
avatarImg = this.createImage(avatarUrl)
|
||||
}
|
||||
|
||||
return html`
|
||||
<li @click=${() => this.getUrl(this.chatInfo)} class="clearfix">
|
||||
${this.isImageLoaded ? html`${avatarImg}` : html``}
|
||||
${!this.isImageLoaded && !this.chatInfo.name && !this.chatInfo.groupName
|
||||
? html`<mwc-icon class="img-icon">account_circle</mwc-icon>`
|
||||
: html``}
|
||||
${!this.isImageLoaded && this.chatInfo.name
|
||||
? html`<div
|
||||
style="width:30px; height:30px; float: left; border-radius:50%; background: ${this.activeChatHeadUrl === this.chatInfo.url
|
||||
? "var(--chatHeadBgActive)"
|
||||
: "var(--chatHeadBg)"}; color: ${this.activeChatHeadUrl ===
|
||||
this.chatInfo.url
|
||||
? "var(--chatHeadTextActive)"
|
||||
: "var(--chatHeadText)"}; font-weight:bold; display: flex; justify-content: center; align-items: center; text-transform: capitalize"
|
||||
>
|
||||
${this.chatInfo.name.charAt(0)}
|
||||
</div>`
|
||||
: ""}
|
||||
${!this.isImageLoaded && this.chatInfo.groupName
|
||||
? html`<div
|
||||
style="width:30px; height:30px; float: left; border-radius:50%; background: ${this.activeChatHeadUrl === this.chatInfo.url
|
||||
? "var(--chatHeadBgActive)"
|
||||
: "var(--chatHeadBg)"}; color: ${this.activeChatHeadUrl === this.chatInfo.url
|
||||
? "var(--chatHeadTextActive)"
|
||||
: "var(--chatHeadText)"}; font-weight:bold; display: flex; justify-content: center; align-items: center; text-transform: capitalize"
|
||||
>
|
||||
${this.chatInfo.groupName.charAt(0)}
|
||||
</div>`
|
||||
: ""}
|
||||
<div>
|
||||
<div class="name">
|
||||
<span style="float:left; padding-left: 8px; color: var(--chat-group);">
|
||||
${this.chatInfo.groupName
|
||||
? this.chatInfo.groupName
|
||||
: this.chatInfo.name !== undefined
|
||||
? this.chatInfo.name
|
||||
: this.chatInfo.address.substr(0, 15)}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
`
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
let configLoaded = false
|
||||
parentEpml.ready().then(() => {
|
||||
parentEpml.subscribe('selected_address', async selectedAddress => {
|
||||
this.selectedAddress = {}
|
||||
selectedAddress = JSON.parse(selectedAddress)
|
||||
if (!selectedAddress || Object.entries(selectedAddress).length === 0) return
|
||||
this.selectedAddress = selectedAddress
|
||||
})
|
||||
parentEpml.subscribe('config', c => {
|
||||
if (!configLoaded) {
|
||||
configLoaded = true
|
||||
}
|
||||
this.config = JSON.parse(c)
|
||||
})
|
||||
})
|
||||
parentEpml.imReady();
|
||||
}
|
||||
|
||||
shouldUpdate(changedProperties) {
|
||||
if(changedProperties.has('activeChatHeadUrl')){
|
||||
return true
|
||||
}
|
||||
if(changedProperties.has('chatInfo')){
|
||||
return true
|
||||
}
|
||||
if(changedProperties.has('isImageLoaded')){
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
getUrl(chatUrl) {
|
||||
this.setActiveChatHeadUrl(chatUrl)
|
||||
}
|
||||
|
||||
onPageNavigation(pageUrl) {
|
||||
parentEpml.request('setPageUrl', pageUrl)
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('chat-side-nav-heads', ChatSideNavHeads)
|
828
qortal-ui-plugins/plugins/core/components/ChatTextEditor copy.js
Normal file
@ -0,0 +1,828 @@
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { get } from 'lit-translate';
|
||||
import { escape, unescape } from 'html-escaper';
|
||||
import { EmojiPicker } from 'emoji-picker-js';
|
||||
import { inputKeyCodes } from '../../utils/keyCodes.js';
|
||||
import { Epml } from '../../../epml.js';
|
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent });
|
||||
class ChatTextEditor extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
isLoading: { type: Boolean },
|
||||
isLoadingMessages: { type: Boolean },
|
||||
_sendMessage: { attribute: false },
|
||||
placeholder: { type: String },
|
||||
imageFile: { type: Object },
|
||||
insertImage: { attribute: false },
|
||||
iframeHeight: { type: Number },
|
||||
editedMessageObj: { type: Object },
|
||||
chatEditor: { type: Object },
|
||||
setChatEditor: { attribute: false },
|
||||
iframeId: { type: String },
|
||||
hasGlobalEvents: { type: Boolean },
|
||||
chatMessageSize: { type: Number },
|
||||
isEditMessageOpen: { type: Boolean },
|
||||
theme: {
|
||||
type: String,
|
||||
reflect: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: auto;
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
.chatbar-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chatbar-caption {
|
||||
border-bottom: 2px solid var(--mdc-theme-primary);
|
||||
}
|
||||
|
||||
.emoji-button {
|
||||
width: 45px;
|
||||
height: 40px;
|
||||
padding-top: 4px;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
max-height: 40px;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.message-size-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.message-size {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 12px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.paperclip-icon {
|
||||
color: var(--paperclip-icon);
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
.paperclip-icon:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.send-icon {
|
||||
width: 30px;
|
||||
margin-left: 5px;
|
||||
transition: all 0.1s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.send-icon:hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.file-picker-container {
|
||||
position: relative;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
.file-picker-input-container {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
z-index: 10;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
input[type=file]::-webkit-file-upload-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chatbar-container textarea {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chatbar-container .chat-editor {
|
||||
display: flex;
|
||||
max-height: -webkit-fill-available;
|
||||
width: 100%;
|
||||
border-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.checkmark-icon {
|
||||
width: 30px;
|
||||
color: var(--mdc-theme-primary);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.checkmark-icon:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.isLoadingMessages = true
|
||||
this.isLoading = false
|
||||
this.getMessageSize = this.getMessageSize.bind(this)
|
||||
this.calculateIFrameHeight = this.calculateIFrameHeight.bind(this)
|
||||
this.resetIFrameHeight = this.resetIFrameHeight.bind(this)
|
||||
this.addGlobalEventListener = this.addGlobalEventListener.bind(this)
|
||||
this.sendMessageFunc = this.sendMessageFunc.bind(this)
|
||||
this.removeGlobalEventListener = this.removeGlobalEventListener.bind(this)
|
||||
this.initialChat = this.initialChat.bind(this)
|
||||
this.iframeHeight = 42
|
||||
this.chatMessageSize = 0
|
||||
this.userName = window.parent.reduxStore.getState().app.accountInfo.names[0]
|
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||
}
|
||||
|
||||
render() {
|
||||
let scrollHeightBool = false;
|
||||
try {
|
||||
if (this.chatMessageInput && this.chatMessageInput.contentDocument.body.scrollHeight > 60 && this.shadowRoot.querySelector(".chat-editor").contentDocument.body.querySelector("#chatbarId").innerHTML.trim() !== "") {
|
||||
scrollHeightBool = true;
|
||||
}
|
||||
} catch (error) {
|
||||
scrollHeightBool = false;
|
||||
}
|
||||
return html`
|
||||
<div
|
||||
class=${["chatbar-container", (this.iframeId === "newChat" || this.iframeId === "privateMessage") ? "chatbar-caption" : ""].join(" ")}
|
||||
style="${scrollHeightBool ? 'align-items: flex-end' : "align-items: center"}">
|
||||
<div
|
||||
style=${this.iframeId === "privateMessage" ? "display: none" : "display: block"}
|
||||
class="file-picker-container"
|
||||
@click=${(e) => {
|
||||
this.preventUserSendingImage(e)
|
||||
}}>
|
||||
<vaadin-icon
|
||||
class="paperclip-icon"
|
||||
icon="vaadin:paperclip"
|
||||
slot="icon"
|
||||
>
|
||||
</vaadin-icon>
|
||||
<div class="file-picker-input-container">
|
||||
<input
|
||||
@change="${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/*" />
|
||||
</div>
|
||||
</div>
|
||||
<textarea style="color: var(--black);" tabindex='1' ?autofocus=${true} ?disabled=${this.isLoading || this.isLoadingMessages} id="messageBox" rows="1"></textarea>
|
||||
<iframe style=${(this.iframeId === "newChat" && this.iframeHeight > 42) && "height: 100%;"} id=${this.iframeId} class="chat-editor" tabindex="-1" height=${this.iframeHeight}></iframe>
|
||||
<button class="emoji-button" ?disabled=${this.isLoading || this.isLoadingMessages}>
|
||||
${html`<img class="emoji" draggable="false" alt="😀" src="/emoji/svg/1f600.svg" />`}
|
||||
</button>
|
||||
${this.editedMessageObj ? (
|
||||
html`
|
||||
<div>
|
||||
${this.isLoading === false ? html`
|
||||
<vaadin-icon
|
||||
class="checkmark-icon"
|
||||
icon="vaadin:check"
|
||||
slot="icon"
|
||||
@click=${() => {
|
||||
this.sendMessageFunc();
|
||||
}}
|
||||
>
|
||||
</vaadin-icon>
|
||||
` :
|
||||
html`
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
) :
|
||||
html`
|
||||
<div
|
||||
style="${scrollHeightBool
|
||||
? 'margin-bottom: 5px;'
|
||||
: "margin-bottom: 0;"}
|
||||
${this.iframeId === 'newChat'
|
||||
? 'display: none;'
|
||||
: 'display: flex;'}">
|
||||
${this.isLoading === false ? html`
|
||||
<img
|
||||
src="/img/qchat-send-message-icon.svg"
|
||||
alt="send-icon"
|
||||
class="send-icon"
|
||||
@click=${() => {
|
||||
this.sendMessageFunc();
|
||||
}}
|
||||
/>
|
||||
` :
|
||||
html`
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
</div>
|
||||
${this.chatMessageSize >= 750 ?
|
||||
html`
|
||||
<div class="message-size-container" style=${this.imageFile && "margin-top: 10px;"}>
|
||||
<div class="message-size" style="${this.chatMessageSize > 1000 && 'color: #bd1515'}">
|
||||
${`Your message size is of ${this.chatMessageSize} bytes out of a maximum of 1000`}
|
||||
</div>
|
||||
</div>
|
||||
` :
|
||||
html``}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
preventUserSendingImage(e) {
|
||||
if (!this.userName) {
|
||||
e.preventDefault();
|
||||
parentEpml.request('showSnackBar', get("chatpage.cchange27"));
|
||||
};
|
||||
}
|
||||
|
||||
initialChat(e) {
|
||||
if (!this.chatEditor?.contentDiv.matches(':focus')) {
|
||||
// WARNING: Deprecated methods from KeyBoard Event
|
||||
if (e.code === "Space" || e.keyCode === 32 || e.which === 32) {
|
||||
this.chatEditor.insertText(' ');
|
||||
} else if (inputKeyCodes.includes(e.keyCode)) {
|
||||
this.chatEditor.insertText(e.key);
|
||||
return this.chatEditor.focus();
|
||||
} else {
|
||||
return this.chatEditor.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addGlobalEventListener(){
|
||||
document.addEventListener('keydown', this.initialChat);
|
||||
}
|
||||
|
||||
removeGlobalEventListener(){
|
||||
document.removeEventListener('keydown', this.initialChat);
|
||||
}
|
||||
|
||||
async firstUpdated() {
|
||||
if (this.hasGlobalEvents) {
|
||||
this.addGlobalEventListener();
|
||||
}
|
||||
|
||||
window.addEventListener('storage', () => {
|
||||
const checkTheme = localStorage.getItem('qortalTheme');
|
||||
const chatbar = this.shadowRoot.getElementById(this.iframeId).contentWindow.document.getElementById('chatbarId');
|
||||
if (checkTheme === 'dark') {
|
||||
this.theme = 'dark';
|
||||
chatbar.style.cssText = "color:#ffffff;"
|
||||
} else {
|
||||
this.theme = 'light';
|
||||
chatbar.style.cssText = "color:#080808;"
|
||||
}
|
||||
})
|
||||
|
||||
this.emojiPickerHandler = this.shadowRoot.querySelector('.emoji-button');
|
||||
this.mirrorChatInput = this.shadowRoot.getElementById('messageBox');
|
||||
this.chatMessageInput = this.shadowRoot.getElementById(this.iframeId);
|
||||
|
||||
this.emojiPicker = new EmojiPicker({
|
||||
style: "twemoji",
|
||||
twemojiBaseUrl: '/emoji/',
|
||||
showPreview: false,
|
||||
showVariants: false,
|
||||
showAnimation: false,
|
||||
position: 'top-start',
|
||||
boxShadow: 'rgba(4, 4, 5, 0.15) 0px 0px 0px 1px, rgba(0, 0, 0, 0.24) 0px 8px 16px 0px',
|
||||
zIndex: 100
|
||||
|
||||
});
|
||||
|
||||
this.emojiPicker.on('emoji', selection => {
|
||||
const emojiHtmlString = `<img class="emoji" draggable="false" alt="${selection.emoji}" src="${selection.url}">`;
|
||||
this.chatEditor.insertEmoji(emojiHtmlString);
|
||||
});
|
||||
|
||||
|
||||
this.emojiPickerHandler.addEventListener('click', () => this.emojiPicker.togglePicker(this.emojiPickerHandler));
|
||||
|
||||
await this.updateComplete;
|
||||
this.initChatEditor();
|
||||
}
|
||||
|
||||
async updated(changedProperties) {
|
||||
if (changedProperties && changedProperties.has('editedMessageObj')) {
|
||||
if (this.editedMessageObj) {
|
||||
this.chatEditor.insertText(this.editedMessageObj.message);
|
||||
this.getMessageSize(this.editedMessageObj.message);
|
||||
} else {
|
||||
this.chatEditor.insertText("");
|
||||
this.chatMessageSize = 0;
|
||||
}
|
||||
}
|
||||
if (changedProperties && changedProperties.has('placeholder')) {
|
||||
const captionEditor = this.shadowRoot.getElementById(this.iframeId).contentWindow.document.getElementById('chatbarId');
|
||||
captionEditor.setAttribute('data-placeholder', this.placeholder);
|
||||
}
|
||||
|
||||
if (changedProperties && changedProperties.has("imageFile")) {
|
||||
this.chatMessageInput = "newChat";
|
||||
}
|
||||
}
|
||||
|
||||
shouldUpdate(changedProperties) {
|
||||
// Only update element if prop1 changed.
|
||||
if(changedProperties.has('setChatEditor') && changedProperties.size === 1) return false
|
||||
return true
|
||||
}
|
||||
|
||||
sendMessageFunc(props) {
|
||||
if (this.chatMessageSize > 1000 ) {
|
||||
parentEpml.request('showSnackBar', get("chatpage.cchange29"));
|
||||
return;
|
||||
};
|
||||
this.chatMessageSize = 0;
|
||||
this.chatEditor.updateMirror();
|
||||
this._sendMessage(props);
|
||||
}
|
||||
|
||||
getMessageSize(message){
|
||||
try {
|
||||
const messageText = message;
|
||||
// Format and Sanitize Message
|
||||
const sanitizedMessage = messageText.replace(/ /gi, ' ').replace(/<br\s*[\/]?>/gi, '\n');
|
||||
const trimmedMessage = sanitizedMessage.trim();
|
||||
let messageObject = {};
|
||||
|
||||
if (this.repliedToMessageObj) {
|
||||
let chatReference = this.repliedToMessageObj.reference;
|
||||
if (this.repliedToMessageObj.chatReference) {
|
||||
chatReference = this.repliedToMessageObj.chatReference;
|
||||
}
|
||||
messageObject = {
|
||||
messageText: trimmedMessage,
|
||||
images: [''],
|
||||
repliedTo: chatReference,
|
||||
version: 1
|
||||
}
|
||||
} else if (this.editedMessageObj) {
|
||||
let message = "";
|
||||
try {
|
||||
const parsedMessageObj = JSON.parse(this.editedMessageObj.decodedMessage);
|
||||
message = parsedMessageObj;
|
||||
} catch (error) {
|
||||
message = this.messageObj.decodedMessage
|
||||
}
|
||||
messageObject = {
|
||||
...message,
|
||||
messageText: trimmedMessage,
|
||||
}
|
||||
} else if(this.imageFile && this.iframeId === 'newChat') {
|
||||
messageObject = {
|
||||
messageText: trimmedMessage,
|
||||
images: [{
|
||||
service: "QCHAT_IMAGE",
|
||||
name: '123456789123456789123456789',
|
||||
identifier: '123456'
|
||||
}],
|
||||
repliedTo: '',
|
||||
version: 1
|
||||
};
|
||||
} else {
|
||||
messageObject = {
|
||||
messageText: trimmedMessage,
|
||||
images: [''],
|
||||
repliedTo: '',
|
||||
version: 1
|
||||
};
|
||||
}
|
||||
|
||||
const stringified = JSON.stringify(messageObject);
|
||||
const size = new Blob([stringified]).size;
|
||||
this.chatMessageSize = size;
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
calculateIFrameHeight(height) {
|
||||
setTimeout(()=> {
|
||||
const editorTest = this.shadowRoot.getElementById(this.iframeId).contentWindow.document.getElementById('chatbarId').scrollHeight;
|
||||
this.iframeHeight = editorTest + 20;
|
||||
}, 50)
|
||||
}
|
||||
resetIFrameHeight(height) {
|
||||
this.iframeHeight = 42;
|
||||
}
|
||||
initChatEditor() {
|
||||
const ChatEditor = function (editorConfig) {
|
||||
const ChatEditor = function () {
|
||||
const editor = this;
|
||||
editor.init();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.getValue = function () {
|
||||
const editor = this;
|
||||
|
||||
if (editor.contentDiv) {
|
||||
return editor.contentDiv.innerHTML;
|
||||
}
|
||||
};
|
||||
|
||||
ChatEditor.prototype.setValue = function (value) {
|
||||
const editor = this;
|
||||
|
||||
if (value) {
|
||||
editor.contentDiv.innerHTML = value;
|
||||
editor.updateMirror();
|
||||
}
|
||||
|
||||
editor.focus();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.resetValue = function () {
|
||||
const editor = this;
|
||||
editor.contentDiv.innerHTML = '';
|
||||
editor.updateMirror();
|
||||
editor.focus();
|
||||
editorConfig.resetIFrameHeight()
|
||||
};
|
||||
|
||||
ChatEditor.prototype.styles = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.styles = document.createElement('style');
|
||||
editor.styles.setAttribute('type', 'text/css');
|
||||
editor.styles.innerText = `
|
||||
html {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.chatbar-body {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chatbar-body::-webkit-scrollbar-track {
|
||||
background-color: whitesmoke;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.chatbar-body::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
border-radius: 7px;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
.chatbar-body::-webkit-scrollbar-thumb {
|
||||
background-color: rgb(180, 176, 176);
|
||||
border-radius: 7px;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.chatbar-body::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgb(148, 146, 146);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
div {
|
||||
font-size: 1rem;
|
||||
line-height: 1.38rem;
|
||||
font-weight: 400;
|
||||
font-family: "Open Sans", helvetica, sans-serif;
|
||||
padding-right: 3px;
|
||||
text-align: left;
|
||||
white-space: break-spaces;
|
||||
word-break: break-word;
|
||||
outline: none;
|
||||
min-height: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
div[contentEditable=true]:empty:before {
|
||||
content: attr(data-placeholder);
|
||||
display: block;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
div[contentEditable=false]{
|
||||
background: rgba(0,0,0,0.1);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
img.emoji {
|
||||
width: 1.7em;
|
||||
height: 1.5em;
|
||||
margin-bottom: -2px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
`;
|
||||
editor.content.head.appendChild(editor.styles);
|
||||
};
|
||||
|
||||
ChatEditor.prototype.enable = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.contentDiv.setAttribute('contenteditable', 'true');
|
||||
editor.focus();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.getMirrorElement = function (){
|
||||
return editor.mirror
|
||||
}
|
||||
|
||||
ChatEditor.prototype.disable = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.contentDiv.setAttribute('contenteditable', 'false');
|
||||
};
|
||||
|
||||
ChatEditor.prototype.state = function () {
|
||||
const editor = this;
|
||||
|
||||
return editor.contentDiv.getAttribute('contenteditable');
|
||||
};
|
||||
|
||||
ChatEditor.prototype.focus = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.contentDiv.focus();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.clearSelection = function () {
|
||||
const editor = this;
|
||||
|
||||
let selection = editor.content.getSelection().toString();
|
||||
if (!/^\s*$/.test(selection)) editor.content.getSelection().removeAllRanges();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.insertEmoji = function (emojiImg) {
|
||||
const editor = this;
|
||||
|
||||
const doInsert = () => {
|
||||
|
||||
if (editor.content.queryCommandSupported("InsertHTML")) {
|
||||
editor.content.execCommand("insertHTML", false, emojiImg);
|
||||
editor.updateMirror();
|
||||
}
|
||||
};
|
||||
|
||||
editor.focus();
|
||||
return doInsert();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.insertText = function (text) {
|
||||
const editor = this;
|
||||
|
||||
const parsedText = editorConfig.emojiPicker.parse(text);
|
||||
const doPaste = () => {
|
||||
|
||||
if (editor.content.queryCommandSupported("InsertHTML")) {
|
||||
editor.content.execCommand("insertHTML", false, parsedText);
|
||||
editor.updateMirror();
|
||||
}
|
||||
};
|
||||
|
||||
editor.focus();
|
||||
return doPaste();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.updateMirror = function () {
|
||||
const editor = this;
|
||||
|
||||
const chatInputValue = editor.getValue();
|
||||
const filteredValue = chatInputValue.replace(/<img.*?alt=".*?/g, '').replace(/".?src=.*?>/g, '');
|
||||
|
||||
let unescapedValue = editorConfig.unescape(filteredValue);
|
||||
editor.mirror.value = unescapedValue;
|
||||
};
|
||||
|
||||
ChatEditor.prototype.listenChanges = function () {
|
||||
|
||||
const editor = this;
|
||||
|
||||
const events = ['drop', 'contextmenu', 'mouseup', 'click', 'touchend', 'keydown', 'blur', 'paste']
|
||||
|
||||
for (let i = 0; i < events.length; i++) {
|
||||
const event = events[i]
|
||||
editor.content.body.addEventListener(event, async function (e) {
|
||||
|
||||
if (e.type === 'click') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
if (e.type === 'paste') {
|
||||
e.preventDefault();
|
||||
const item_list = await navigator.clipboard.read();
|
||||
let image_type; // we will feed this later
|
||||
const item = item_list.find( item => // choose the one item holding our image
|
||||
item.types.some( type => {
|
||||
if (type.startsWith( 'image/')) {
|
||||
image_type = type;
|
||||
return true;
|
||||
}
|
||||
})
|
||||
);
|
||||
if(item){
|
||||
const blob = item && await item.getType( image_type );
|
||||
var file = new File([blob], "name", {
|
||||
type: image_type
|
||||
});
|
||||
|
||||
editorConfig.insertImage(file)
|
||||
} else {
|
||||
navigator.clipboard.readText()
|
||||
.then(clipboardText => {
|
||||
let escapedText = editorConfig.escape(clipboardText);
|
||||
editor.insertText(escapedText);
|
||||
})
|
||||
.then(() => {
|
||||
editorConfig.getMessageSize(editorConfig.editableElement.contentDocument.body.querySelector("#chatbarId").innerHTML);
|
||||
})
|
||||
.catch(err => {
|
||||
// Fallback if everything fails...
|
||||
let textData = (e.originalEvent || e).clipboardData.getData('text/plain');
|
||||
editor.insertText(textData);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.type === 'contextmenu') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.type === 'keydown') {
|
||||
await new Promise((res, rej) => {
|
||||
setTimeout(() => {
|
||||
editorConfig.calculateIFrameHeight(editorConfig.editableElement.contentDocument.body.scrollHeight);
|
||||
editorConfig.getMessageSize(editorConfig.editableElement.contentDocument.body.querySelector("#chatbarId").innerHTML);
|
||||
}, 0);
|
||||
res();
|
||||
})
|
||||
|
||||
// Handle Enter
|
||||
if (e.keyCode === 13 && !e.shiftKey) {
|
||||
|
||||
|
||||
if (editor.state() === 'false') return false;
|
||||
if (editorConfig.iframeId === 'newChat') {
|
||||
editorConfig.sendFunc(
|
||||
{
|
||||
type: 'image',
|
||||
imageFile: editorConfig.imageFile,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
editorConfig.sendFunc();
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle Commands with CTR or CMD
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
switch (e.keyCode) {
|
||||
case 66:
|
||||
case 98: e.preventDefault();
|
||||
return false;
|
||||
case 73:
|
||||
case 105: e.preventDefault();
|
||||
return false;
|
||||
case 85:
|
||||
case 117: e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.type === 'blur') {
|
||||
editor.clearSelection();
|
||||
}
|
||||
|
||||
if (e.type === 'drop') {
|
||||
e.preventDefault();
|
||||
|
||||
let droppedText = e.dataTransfer.getData('text/plain')
|
||||
let escapedText = editorConfig.escape(droppedText)
|
||||
|
||||
editor.insertText(escapedText);
|
||||
return false;
|
||||
}
|
||||
|
||||
editor.updateMirror();
|
||||
});
|
||||
}
|
||||
|
||||
editor.content.addEventListener('click', function (event) {
|
||||
event.preventDefault();
|
||||
editor.focus();
|
||||
});
|
||||
};
|
||||
|
||||
ChatEditor.prototype.remove = function () {
|
||||
const editor = this;
|
||||
var old_element = editor.content.body;
|
||||
var new_element = old_element.cloneNode(true);
|
||||
editor.content.body.parentNode.replaceChild(new_element, old_element);
|
||||
while (editor.content.body.firstChild) {
|
||||
editor.content.body.removeChild(editor.content.body.lastChild);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ChatEditor.prototype.init = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.frame = editorConfig.editableElement;
|
||||
editor.mirror = editorConfig.mirrorElement;
|
||||
|
||||
editor.content = (editor.frame.contentDocument || editor.frame.document);
|
||||
editor.content.body.classList.add("chatbar-body");
|
||||
|
||||
let elemDiv = document.createElement('div');
|
||||
elemDiv.setAttribute('contenteditable', 'true');
|
||||
elemDiv.setAttribute('spellcheck', 'false');
|
||||
elemDiv.setAttribute('data-placeholder', editorConfig.placeholder);
|
||||
elemDiv.style.cssText = `width:100%; ${editorConfig.theme === "dark" ? "color:#ffffff;" : "color: #080808"}`;
|
||||
elemDiv.id = 'chatbarId';
|
||||
editor.content.body.appendChild(elemDiv);
|
||||
editor.contentDiv = editor.frame.contentDocument.body.firstChild;
|
||||
editor.styles();
|
||||
editor.listenChanges();
|
||||
|
||||
};
|
||||
|
||||
|
||||
function doInit() {
|
||||
return new ChatEditor();
|
||||
}
|
||||
return doInit();
|
||||
};
|
||||
|
||||
const editorConfig = {
|
||||
getMessageSize: this.getMessageSize,
|
||||
calculateIFrameHeight: this.calculateIFrameHeight,
|
||||
mirrorElement: this.mirrorChatInput,
|
||||
editableElement: this.chatMessageInput,
|
||||
sendFunc: this.sendMessageFunc,
|
||||
emojiPicker: this.emojiPicker,
|
||||
escape: escape,
|
||||
unescape: unescape,
|
||||
placeholder: this.placeholder,
|
||||
imageFile: this.imageFile,
|
||||
requestUpdate: this.requestUpdate,
|
||||
insertImage: this.insertImage,
|
||||
chatMessageSize: this.chatMessageSize,
|
||||
addGlobalEventListener: this.addGlobalEventListener,
|
||||
removeGlobalEventListener: this.removeGlobalEventListener,
|
||||
iframeId: this.iframeId,
|
||||
theme: this.theme,
|
||||
resetIFrameHeight: this.resetIFrameHeight
|
||||
};
|
||||
const newChat = new ChatEditor(editorConfig);
|
||||
this.setChatEditor(newChat);
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define("chat-text-editor", ChatTextEditor)
|
677
qortal-ui-plugins/plugins/core/components/ChatTextEditor.js
Normal file
@ -0,0 +1,677 @@
|
||||
import { LitElement, html, css } from "lit";
|
||||
import { get, translate } from 'lit-translate';
|
||||
|
||||
import { EmojiPicker } from 'emoji-picker-js';
|
||||
import { Epml } from '../../../epml.js';
|
||||
import '@material/mwc-icon'
|
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent });
|
||||
class ChatTextEditor extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
isLoading: { type: Boolean },
|
||||
isLoadingMessages: { type: Boolean },
|
||||
_sendMessage: { attribute: false },
|
||||
placeholder: { type: String },
|
||||
imageFile: { type: Object },
|
||||
insertImage: { attribute: false },
|
||||
iframeHeight: { type: Number },
|
||||
editedMessageObj: { type: Object },
|
||||
repliedToMessageObj: {type: Object},
|
||||
setChatEditor: { attribute: false },
|
||||
iframeId: { type: String },
|
||||
hasGlobalEvents: { type: Boolean },
|
||||
chatMessageSize: { type: Number },
|
||||
isEditMessageOpen: { type: Boolean },
|
||||
editor: {type: Object},
|
||||
theme: {
|
||||
type: String,
|
||||
reflect: true
|
||||
},
|
||||
toggleEnableChatEnter: {attribute: false},
|
||||
isEnabledChatEnter: {type: Boolean}
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: auto;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
}
|
||||
.chatbar-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chatbar-caption {
|
||||
border-bottom: 2px solid var(--mdc-theme-primary);
|
||||
}
|
||||
.privateMessageMargin {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.emoji-button {
|
||||
width: 45px;
|
||||
height: 40px;
|
||||
padding-top: 4px;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
max-height: 40px;
|
||||
color: var(--black);
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.message-size-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.message-size {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 12px;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.paperclip-icon {
|
||||
color: var(--paperclip-icon);
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
.paperclip-icon:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.send-icon {
|
||||
width: 30px;
|
||||
margin-left: 5px;
|
||||
transition: all 0.1s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.send-icon:hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.file-picker-container {
|
||||
position: relative;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.file-picker-input-container {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
z-index: 10;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
input[type=file]::-webkit-file-upload-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chatbar-container textarea {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chatbar-container .chat-editor {
|
||||
display: flex;
|
||||
max-height: -webkit-fill-available;
|
||||
width: 100%;
|
||||
border-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.checkmark-icon {
|
||||
width: 30px;
|
||||
color: var(--mdc-theme-primary);
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.checkmark-icon:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.element {
|
||||
width: 100%;
|
||||
max-height: 100%;
|
||||
overflow: auto;
|
||||
color: var(--black);
|
||||
padding: 0px 10px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.element::-webkit-scrollbar-track {
|
||||
background-color: whitesmoke;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.element::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
border-radius: 7px;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
.element::-webkit-scrollbar-thumb {
|
||||
background-color: rgb(180, 176, 176);
|
||||
border-radius: 7px;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.element::-webkit-scrollbar-thumb:hover {
|
||||
background-color: rgb(148, 146, 146);
|
||||
cursor: pointer;
|
||||
}
|
||||
.ProseMirror:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.is-active {
|
||||
background-color: var(--white)
|
||||
}
|
||||
|
||||
.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 img {
|
||||
width: 1.7em;
|
||||
height: 1.5em;
|
||||
margin: 0px;
|
||||
|
||||
}
|
||||
|
||||
.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%);
|
||||
|
||||
}
|
||||
|
||||
.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 {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.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() {
|
||||
super()
|
||||
this.isLoadingMessages = true
|
||||
this.isLoading = false
|
||||
this.getMessageSize = this.getMessageSize.bind(this)
|
||||
this.sendMessageFunc = this.sendMessageFunc.bind(this)
|
||||
this.iframeHeight = 42
|
||||
this.chatMessageSize = 0
|
||||
this.userName = window.parent.reduxStore.getState().app.accountInfo.names[0]
|
||||
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
|
||||
this.editor = null
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
return html`
|
||||
<div
|
||||
class=${["chatbar-container", "chatbar-buttons", this.iframeId !=="_chatEditorDOM" && 'hide-styling'].join(" ")}
|
||||
style="align-items: center;">
|
||||
|
||||
<button
|
||||
@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(" ")}
|
||||
>
|
||||
<!-- <mwc-icon >format_bold</mwc-icon> -->
|
||||
<span class="material-symbols-outlined"></span>
|
||||
</button>
|
||||
<button
|
||||
@click=${() => this.editor.chain().focus().toggleItalic().run()}
|
||||
?disabled=${ this.editor &&
|
||||
!this.editor.can()
|
||||
.chain()
|
||||
.focus()
|
||||
.toggleItalic()
|
||||
.run()
|
||||
}
|
||||
class=${["chatbar-button-single", (this.editedMessageObj || this.repliedToMessageObj) && 'show-chatbar-buttons', this.editor && this.editor.isActive('italic') ? 'is-active' : ''].join(' ')}
|
||||
>
|
||||
<span class="material-symbols-outlined"></span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
@click=${() => this.editor.chain().focus().toggleUnderline().run()}
|
||||
class=${["chatbar-button-single", (this.editedMessageObj || this.repliedToMessageObj) && 'show-chatbar-buttons', this.editor && this.editor.isActive('underline') ? 'is-active' : ''].join(' ')}
|
||||
>
|
||||
<span class="material-symbols-outlined"></span>
|
||||
</button>
|
||||
<button
|
||||
@click=${() => this.editor.chain().focus().toggleHighlight().run()}
|
||||
class=${["chatbar-button-single", (this.editedMessageObj || this.repliedToMessageObj) && 'show-chatbar-buttons', this.editor && this.editor.isActive('highlight') ? 'is-active' : ''].join(' ')}
|
||||
>
|
||||
<span class="material-symbols-outlined"></span>
|
||||
</button>
|
||||
<button
|
||||
@click=${() => this.editor.chain().focus().toggleCodeBlock().run()}
|
||||
class=${["chatbar-button-single",(this.editedMessageObj || this.repliedToMessageObj) && 'show-chatbar-buttons', this.editor && this.editor.isActive('codeBlock') ? 'is-active' : ''].join(' ')}
|
||||
>
|
||||
<span class="material-symbols-outlined"></span>
|
||||
</button>
|
||||
<button
|
||||
@click=${()=> this.toggleEnableChatEnter() }
|
||||
style="height: 26px; box-sizing: border-box;"
|
||||
class=${["chatbar-button-single",(this.editedMessageObj || this.repliedToMessageObj) && 'show-chatbar-buttons', this.editor && this.editor.isActive('codeBlock') ? 'is-active' : ''].join(' ')}
|
||||
>
|
||||
${this.isEnabledChatEnter ? html`
|
||||
${translate("chatpage.cchange63")}
|
||||
` : html`
|
||||
${translate("chatpage.cchange64")}
|
||||
`}
|
||||
|
||||
</button>
|
||||
|
||||
</div>
|
||||
<div
|
||||
class=${["chatbar-container", (this.iframeId === "newChat" || this.iframeId === "privateMessage") ? "chatbar-caption" : ""].join(" ")}
|
||||
style="align-items: flex-end; position: relative">
|
||||
|
||||
<div
|
||||
style=${this.iframeId === "privateMessage" ? "display: none" : "display: block"}
|
||||
class="file-picker-container"
|
||||
@click=${(e) => {
|
||||
this.preventUserSendingImage(e)
|
||||
}}>
|
||||
<vaadin-icon
|
||||
class="paperclip-icon"
|
||||
icon="vaadin:paperclip"
|
||||
slot="icon"
|
||||
>
|
||||
</vaadin-icon>
|
||||
<div class="file-picker-input-container">
|
||||
<input
|
||||
@change="${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/*" />
|
||||
</div>
|
||||
</div>
|
||||
<textarea style="color: var(--black);" tabindex='1' ?autofocus=${true} ?disabled=${this.isLoading || this.isLoadingMessages} id="messageBox" rows="1"></textarea>
|
||||
<div id=${this.iframeId}
|
||||
class=${["element", this.iframeId === "privateMessage" ? "privateMessageMargin" : ""].join(" ")}
|
||||
></div>
|
||||
<button class="emoji-button" ?disabled=${this.isLoading || this.isLoadingMessages}>
|
||||
${html`<img class="emoji" draggable="false" alt="😀" src="/emoji/svg/1f600.svg" />`}
|
||||
</button>
|
||||
${this.editedMessageObj ? (
|
||||
html`
|
||||
<div style="margin-bottom: 10px">
|
||||
${this.isLoading === false ? html`
|
||||
<vaadin-icon
|
||||
class="checkmark-icon"
|
||||
icon="vaadin:check"
|
||||
slot="icon"
|
||||
@click=${() => {
|
||||
this.sendMessageFunc();
|
||||
}}
|
||||
>
|
||||
</vaadin-icon>
|
||||
` :
|
||||
html`
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
) :
|
||||
html`
|
||||
<div
|
||||
style="margin-bottom: 10px;
|
||||
${this.iframeId === 'newChat'
|
||||
? 'display: none;'
|
||||
: 'display: flex;'}">
|
||||
${this.isLoading === false ? html`
|
||||
<img
|
||||
src="/img/qchat-send-message-icon.svg"
|
||||
alt="send-icon"
|
||||
class="send-icon"
|
||||
@click=${() => {
|
||||
this.sendMessageFunc();
|
||||
}}
|
||||
/>
|
||||
` :
|
||||
html`
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
</div>
|
||||
${this.chatMessageSize >= 750 ?
|
||||
html`
|
||||
<div class="message-size-container" style=${this.imageFile && "margin-top: 10px;"}>
|
||||
<div class="message-size" style="${this.chatMessageSize > 4000 && 'color: #bd1515'}">
|
||||
${`Your message size is of ${this.chatMessageSize} bytes out of a maximum of 4000`}
|
||||
</div>
|
||||
</div>
|
||||
` :
|
||||
html``}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
preventUserSendingImage(e) {
|
||||
if (!this.userName) {
|
||||
e.preventDefault();
|
||||
parentEpml.request('showSnackBar', get("chatpage.cchange27"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
async firstUpdated() {
|
||||
|
||||
|
||||
|
||||
window.addEventListener('storage', () => {
|
||||
const checkTheme = localStorage.getItem('qortalTheme');
|
||||
const chatbar = this.shadowRoot.querySelector('.element')
|
||||
if (checkTheme === 'dark') {
|
||||
this.theme = 'dark';
|
||||
chatbar.style.cssText = "color:#ffffff;"
|
||||
|
||||
} else {
|
||||
this.theme = 'light';
|
||||
chatbar.style.cssText = "color:#080808;"
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
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",
|
||||
twemojiBaseUrl: '/emoji/',
|
||||
showPreview: false,
|
||||
showVariants: false,
|
||||
showAnimation: false,
|
||||
position: 'top-start',
|
||||
boxShadow: 'rgba(4, 4, 5, 0.15) 0px 0px 0px 1px, rgba(0, 0, 0, 0.24) 0px 8px 16px 0px',
|
||||
zIndex: 100
|
||||
|
||||
});
|
||||
|
||||
this.emojiPicker.on('emoji', selection => {
|
||||
|
||||
this.editor.commands.insertContent(selection.emoji, {
|
||||
parseOptions: {
|
||||
preserveWhitespace: false
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
this.emojiPickerHandler.addEventListener('click', () => this.emojiPicker.togglePicker(this.emojiPickerHandler));
|
||||
|
||||
await this.updateComplete;
|
||||
// this.initChatEditor();
|
||||
}
|
||||
|
||||
async updated(changedProperties) {
|
||||
if (changedProperties && changedProperties.has('editedMessageObj')) {
|
||||
if (this.editedMessageObj) {
|
||||
this.editor.commands.setContent(this.editedMessageObj.message)
|
||||
this.getMessageSize(this.editedMessageObj.message);
|
||||
} else {
|
||||
this.chatMessageSize = 0;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
// Only update element if prop1 changed.
|
||||
if(changedProperties.has('setChatEditor') && changedProperties.size === 1) return false
|
||||
return true
|
||||
}
|
||||
|
||||
sendMessageFunc(props) {
|
||||
if(this.editor.isEmpty && this.iframeId !== 'newChat') return
|
||||
this.getMessageSize(this.editor.getJSON())
|
||||
if (this.chatMessageSize > 4000 ) {
|
||||
parentEpml.request('showSnackBar', get("chatpage.cchange29"));
|
||||
return;
|
||||
}
|
||||
this.chatMessageSize = 0;
|
||||
this._sendMessage(props, this.editor.getJSON());
|
||||
}
|
||||
|
||||
getMessageSize(message){
|
||||
try {
|
||||
|
||||
const trimmedMessage = message
|
||||
let messageObject = {};
|
||||
|
||||
if (this.repliedToMessageObj) {
|
||||
let chatReference = this.repliedToMessageObj.reference;
|
||||
if (this.repliedToMessageObj.chatReference) {
|
||||
chatReference = this.repliedToMessageObj.chatReference;
|
||||
}
|
||||
messageObject = {
|
||||
messageText: trimmedMessage,
|
||||
images: [''],
|
||||
repliedTo: chatReference,
|
||||
version: 2
|
||||
}
|
||||
} else if (this.editedMessageObj) {
|
||||
let message = "";
|
||||
try {
|
||||
const parsedMessageObj = JSON.parse(this.editedMessageObj.decodedMessage);
|
||||
message = parsedMessageObj;
|
||||
} catch (error) {
|
||||
message = this.messageObj.decodedMessage
|
||||
}
|
||||
messageObject = {
|
||||
...message,
|
||||
messageText: trimmedMessage,
|
||||
}
|
||||
} else if(this.imageFile && this.iframeId === 'newChat') {
|
||||
messageObject = {
|
||||
messageText: trimmedMessage,
|
||||
images: [{
|
||||
service: "QCHAT_IMAGE",
|
||||
name: '123456789123456789123456789',
|
||||
identifier: '123456'
|
||||
}],
|
||||
repliedTo: '',
|
||||
version: 2
|
||||
};
|
||||
} else {
|
||||
messageObject = {
|
||||
messageText: trimmedMessage,
|
||||
images: [''],
|
||||
repliedTo: '',
|
||||
version: 2
|
||||
};
|
||||
}
|
||||
|
||||
const stringified = JSON.stringify(messageObject);
|
||||
const size = new Blob([stringified]).size;
|
||||
this.chatMessageSize = size;
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
window.customElements.define("chat-text-editor", ChatTextEditor)
|
@ -25,7 +25,8 @@ class ChatWelcomePage extends LitElement {
|
||||
btnDisable: { type: Boolean },
|
||||
isLoading: { type: Boolean },
|
||||
balance: { type: Number },
|
||||
theme: { type: String, reflect: true }
|
||||
theme: { type: String, reflect: true },
|
||||
setOpenPrivateMessage: { attribute: false }
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,7 +213,14 @@ class ChatWelcomePage extends LitElement {
|
||||
<div class="center-box">
|
||||
<mwc-icon class="img-icon">chat</mwc-icon><br>
|
||||
<span style="font-size: 20px; color: var(--black);">${this.myAddress.address}</span>
|
||||
<div class="start-chat" @click=${() => this.shadowRoot.querySelector('#startSecondChatDialog').show()}>${translate("welcomepage.wcchange2")}</div>
|
||||
<div
|
||||
class="start-chat"
|
||||
@click="${() => this.setOpenPrivateMessage({
|
||||
name: "",
|
||||
open: true
|
||||
})}">
|
||||
${translate("welcomepage.wcchange2")}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -230,7 +238,11 @@ class ChatWelcomePage extends LitElement {
|
||||
<textarea class="textarea" @keydown=${(e) => this._textArea(e)} ?disabled=${this.isLoading} id="messageBox" placeholder="${translate("welcomepage.wcchange5")}" rows="1"></textarea>
|
||||
</p>
|
||||
|
||||
<mwc-button ?disabled="${this.isLoading}" slot="primaryAction" @click=${this._sendMessage}>${translate("welcomepage.wcchange6")}</mwc-button>
|
||||
<mwc-button ?disabled="${this.isLoading}" slot="primaryAction" @click=${() => {
|
||||
this._sendMessage();
|
||||
}
|
||||
}>
|
||||
${translate("welcomepage.wcchange6")}</mwc-button>
|
||||
<mwc-button
|
||||
?disabled="${this.isLoading}"
|
||||
slot="secondaryAction"
|
||||
@ -319,90 +331,90 @@ class ChatWelcomePage extends LitElement {
|
||||
}
|
||||
|
||||
_sendMessage() {
|
||||
this.isLoading = true
|
||||
|
||||
const recipient = this.shadowRoot.getElementById('sendTo').value
|
||||
const messageBox = this.shadowRoot.getElementById('messageBox')
|
||||
const messageText = messageBox.value
|
||||
this.isLoading = true;
|
||||
const recipient = this.shadowRoot.getElementById('sendTo').value;
|
||||
const messageBox = this.shadowRoot.getElementById('messageBox');
|
||||
const messageText = messageBox.value;
|
||||
|
||||
if (recipient.length === 0) {
|
||||
this.isLoading = false
|
||||
this.isLoading = false;
|
||||
} else if (messageText.length === 0) {
|
||||
this.isLoading = false
|
||||
this.isLoading = false;
|
||||
} else {
|
||||
this.sendMessage()
|
||||
}
|
||||
this.sendMessage();
|
||||
}
|
||||
};
|
||||
|
||||
async sendMessage(e) {
|
||||
this.isLoading = true
|
||||
|
||||
const _recipient = this.shadowRoot.getElementById('sendTo').value
|
||||
const messageBox = this.shadowRoot.getElementById('messageBox')
|
||||
const messageText = messageBox.value
|
||||
let recipient
|
||||
async sendMessage() {
|
||||
this.isLoading = true;
|
||||
const _recipient = this.shadowRoot.getElementById('sendTo').value;
|
||||
const messageBox = this.shadowRoot.getElementById('messageBox');
|
||||
const messageText = messageBox.value;
|
||||
let recipient;
|
||||
|
||||
const validateName = async (receiverName) => {
|
||||
let myRes
|
||||
let myRes;
|
||||
let myNameRes = await parentEpml.request('apiCall', {
|
||||
type: 'api',
|
||||
url: `/names/${receiverName}`
|
||||
})
|
||||
|
||||
});
|
||||
if (myNameRes.error === 401) {
|
||||
myRes = false
|
||||
myRes = false;
|
||||
} else {
|
||||
myRes = myNameRes
|
||||
}
|
||||
myRes = myNameRes;
|
||||
};
|
||||
return myRes;
|
||||
};
|
||||
|
||||
return myRes
|
||||
}
|
||||
const myNameRes = await validateName(_recipient);
|
||||
|
||||
const myNameRes = await validateName(_recipient)
|
||||
if (!myNameRes) {
|
||||
|
||||
recipient = _recipient
|
||||
recipient = _recipient;
|
||||
} else {
|
||||
|
||||
recipient = myNameRes.owner
|
||||
}
|
||||
recipient = myNameRes.owner;
|
||||
};
|
||||
|
||||
let _reference = new Uint8Array(64);
|
||||
window.crypto.getRandomValues(_reference);
|
||||
|
||||
let sendTimestamp = Date.now()
|
||||
let sendTimestamp = Date.now();
|
||||
|
||||
let reference = window.parent.Base58.encode(_reference)
|
||||
let reference = window.parent.Base58.encode(_reference);
|
||||
|
||||
const getAddressPublicKey = async () => {
|
||||
let isEncrypted
|
||||
let _publicKey
|
||||
let isEncrypted;
|
||||
let _publicKey;
|
||||
|
||||
let addressPublicKey = await parentEpml.request('apiCall', {
|
||||
type: 'api',
|
||||
url: `/addresses/publickey/${recipient}`
|
||||
})
|
||||
|
||||
|
||||
if (addressPublicKey.error === 102) {
|
||||
_publicKey = false
|
||||
_publicKey = false;
|
||||
// Do something here...
|
||||
let err1string = get("welcomepage.wcchange7")
|
||||
parentEpml.request('showSnackBar', `${err1string}`)
|
||||
this.isLoading = false
|
||||
let err1string = get("welcomepage.wcchange7");
|
||||
parentEpml.request('showSnackBar', `${err1string}`);
|
||||
this.isLoading = false;
|
||||
} else if (addressPublicKey !== false) {
|
||||
isEncrypted = 1
|
||||
_publicKey = addressPublicKey
|
||||
sendMessageRequest(isEncrypted, _publicKey)
|
||||
isEncrypted = 1;
|
||||
_publicKey = addressPublicKey;
|
||||
sendMessageRequest(isEncrypted, _publicKey);
|
||||
} else {
|
||||
isEncrypted = 0
|
||||
_publicKey = this.selectedAddress.address
|
||||
sendMessageRequest(isEncrypted, _publicKey)
|
||||
}
|
||||
isEncrypted = 0;
|
||||
_publicKey = this.selectedAddress.address;
|
||||
sendMessageRequest(isEncrypted, _publicKey);
|
||||
};
|
||||
};
|
||||
|
||||
const sendMessageRequest = async (isEncrypted, _publicKey) => {
|
||||
|
||||
const messageObject = {
|
||||
messageText,
|
||||
images: [''],
|
||||
repliedTo: '',
|
||||
version: 1
|
||||
};
|
||||
const stringifyMessageObject = JSON.stringify(messageObject);
|
||||
let chatResponse = await parentEpml.request('chat', {
|
||||
type: 18,
|
||||
nonce: this.selectedAddress.nonce,
|
||||
@ -411,14 +423,13 @@ class ChatWelcomePage extends LitElement {
|
||||
recipient: recipient,
|
||||
recipientPublicKey: _publicKey,
|
||||
hasChatReference: 0,
|
||||
message: messageText,
|
||||
message: stringifyMessageObject,
|
||||
lastReference: reference,
|
||||
proofOfWorkNonce: 0,
|
||||
isEncrypted: isEncrypted,
|
||||
isText: 1
|
||||
}
|
||||
})
|
||||
|
||||
_computePow(chatResponse)
|
||||
}
|
||||
|
||||
|
@ -48,32 +48,42 @@ class LevelFounder extends LitElement {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.level {
|
||||
position: relative;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.custom {
|
||||
--paper-tooltip-background: #03a9f4;
|
||||
--paper-tooltip-text-color: #fff;
|
||||
}
|
||||
|
||||
.level-img-tooltip {
|
||||
--paper-tooltip-background: #000000;
|
||||
--paper-tooltip-text-color: #fff;
|
||||
--paper-tooltip-delay-in: 300;
|
||||
--paper-tooltip-delay-out: 3000;
|
||||
}
|
||||
|
||||
.message-data {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.message-data-level {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
|
||||
.badge {
|
||||
align-items: center;
|
||||
background: #03a9f4;
|
||||
background: rgb(3, 169, 244);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 99em;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
color: rgb(255, 255, 255);
|
||||
display: flex;
|
||||
font-size: 10px;
|
||||
font-weight: 400;
|
||||
height: 12px;
|
||||
width: 12px;
|
||||
justify-content: center;
|
||||
line-height: 1;
|
||||
min-width: 12px;
|
||||
position: absolute;
|
||||
left: -16px;
|
||||
top: -12px;
|
||||
cursor: pointer;
|
||||
}
|
||||
`
|
||||
@ -87,7 +97,7 @@ class LevelFounder extends LitElement {
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="level">
|
||||
<div class="message-data">
|
||||
${this.renderFounder()}
|
||||
${this.renderLevel()}
|
||||
</div>
|
||||
@ -135,21 +145,24 @@ class LevelFounder extends LitElement {
|
||||
}
|
||||
|
||||
renderFounder() {
|
||||
let adressfounder = this.memberInfo.flags
|
||||
let adressfounder = this.memberInfo.flags;
|
||||
if (adressfounder === 1) {
|
||||
return html `
|
||||
<span id="founderTooltip" class="badge">F</span>
|
||||
<paper-tooltip class="custom" for="founderTooltip" position="top">FOUNDER</paper-tooltip>
|
||||
`
|
||||
} else {
|
||||
return html ``
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
renderLevel() {
|
||||
let adresslevel = this.memberInfo.level
|
||||
let adresslevel = this.memberInfo.level;
|
||||
return html `
|
||||
<span id="levelTooltip">${translate("mintingpage.mchange27")} ${adresslevel}</span>
|
||||
<img id="level-img" src=${`/img/badges/level-${adresslevel}.png`} alt=${`badge-${adresslevel}`} class="message-data-level" />
|
||||
<paper-tooltip class="level-img-tooltip" for="level-img" position="top" >
|
||||
${translate("mintingpage.mchange27")} ${adresslevel}
|
||||
</paper-tooltip>
|
||||
`
|
||||
}
|
||||
|
||||
|
@ -232,7 +232,11 @@ class NameMenu extends LitElement {
|
||||
<p style="margin-bottom:0;">
|
||||
<textarea class="textarea" @keydown=${(e) => this._textArea(e)} ?disabled=${this.isLoading} id="messageBox" placeholder="${translate("welcomepage.wcchange5")}" rows="1"></textarea>
|
||||
</p>
|
||||
<mwc-button ?disabled="${this.isLoading}" slot="primaryAction" @click=${this._sendMessage}>${translate("welcomepage.wcchange6")}</mwc-button>
|
||||
<mwc-button ?disabled="${this.isLoading}" slot="primaryAction" @click=${() => {
|
||||
this._sendMessage();
|
||||
}
|
||||
}>
|
||||
${translate("welcomepage.wcchange6")}</mwc-button>
|
||||
<mwc-button
|
||||
?disabled="${this.isLoading}"
|
||||
slot="secondaryAction"
|
||||
@ -246,15 +250,15 @@ class NameMenu extends LitElement {
|
||||
}
|
||||
|
||||
firstUpdated() {
|
||||
this.getChatBlockedAdresses()
|
||||
this.getChatBlockedAdresses();
|
||||
|
||||
setInterval(() => {
|
||||
this.getChatBlockedAdresses();
|
||||
}, 60000)
|
||||
|
||||
window.addEventListener('storage', () => {
|
||||
const checkLanguage = localStorage.getItem('qortalLanguage')
|
||||
use(checkLanguage)
|
||||
const checkLanguage = localStorage.getItem('qortalLanguage');
|
||||
use(checkLanguage);
|
||||
})
|
||||
|
||||
window.onclick = function(event) {
|
||||
@ -521,7 +525,13 @@ class NameMenu extends LitElement {
|
||||
};
|
||||
|
||||
const sendMessageRequest = async (isEncrypted, _publicKey) => {
|
||||
|
||||
const messageObject = {
|
||||
messageText,
|
||||
images: [''],
|
||||
repliedTo: '',
|
||||
version: 1
|
||||
}
|
||||
const stringifyMessageObject = JSON.stringify(messageObject)
|
||||
let chatResponse = await parentEpml.request('chat', {
|
||||
type: 18,
|
||||
nonce: this.selectedAddress.nonce,
|
||||
@ -530,7 +540,7 @@ class NameMenu extends LitElement {
|
||||
recipient: recipient,
|
||||
recipientPublicKey: _publicKey,
|
||||
hasChatReference: 0,
|
||||
message: messageText,
|
||||
message: stringifyMessageObject,
|
||||
lastReference: reference,
|
||||
proofOfWorkNonce: 0,
|
||||
isEncrypted: isEncrypted,
|
||||
|
85
qortal-ui-plugins/plugins/core/components/TipUser-css.js
Normal file
@ -0,0 +1,85 @@
|
||||
import { css } from 'lit'
|
||||
|
||||
export const tipUserStyles = css`
|
||||
.tip-user-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 12px;
|
||||
border-bottom: 1px solid whitesmoke;
|
||||
gap: 25px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tip-user-header-font {
|
||||
font-family: Montserrat, sans-serif;
|
||||
font-size: 20px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.tip-user-body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20px 10px;
|
||||
flex-direction: column;
|
||||
gap: 25px;
|
||||
}
|
||||
|
||||
.tip-input {
|
||||
width: 300px;
|
||||
margin-bottom: 15px;
|
||||
outline: 0;
|
||||
border-width: 0 0 2px;
|
||||
border-color: var(--mdc-theme-primary);
|
||||
background-color: transparent;
|
||||
padding: 10px;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 15px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
}
|
||||
|
||||
.tip-input::selection {
|
||||
background-color: var(--mdc-theme-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.tip-input::placeholder {
|
||||
opacity: 0.9;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.tip-available {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 17px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
font-weight: 300;
|
||||
letter-spacing: 0.3px;
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.success-msg {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.3px;
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
color: #10880b;
|
||||
}
|
||||
|
||||
.error-msg {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.3px;
|
||||
margin: 0;
|
||||
user-select: none;
|
||||
color: #f30000;
|
||||
}
|
||||
`
|
277
qortal-ui-plugins/plugins/core/components/TipUser.js
Normal file
@ -0,0 +1,277 @@
|
||||
import { LitElement, html } from 'lit';
|
||||
import { render } from 'lit/html.js';
|
||||
import { get, translate } from 'lit-translate';
|
||||
import { tipUserStyles } from './TipUser-css.js';
|
||||
import { Epml } from '../../../epml';
|
||||
import '@vaadin/button';
|
||||
import '@polymer/paper-progress/paper-progress.js';
|
||||
|
||||
const parentEpml = new Epml({ type: "WINDOW", source: window.parent });
|
||||
|
||||
export class TipUser extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
userName: { type: String },
|
||||
walletBalance: { type: Number },
|
||||
sendMoneyLoading: { type: Boolean },
|
||||
closeTipUser: { type: Boolean },
|
||||
btnDisable: { type: Boolean },
|
||||
errorMessage: { type: String },
|
||||
successMessage: { type: String },
|
||||
setOpenTipUser: { attribute: false },
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.sendMoneyLoading = false
|
||||
this.btnDisable = false
|
||||
this.errorMessage = ""
|
||||
this.successMessage = ""
|
||||
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress
|
||||
}
|
||||
|
||||
static styles = [tipUserStyles]
|
||||
|
||||
async firstUpdated() {
|
||||
await this.fetchWalletDetails();
|
||||
}
|
||||
|
||||
updated(changedProperties) {
|
||||
if (changedProperties && changedProperties.has("closeTipUser")) {
|
||||
if (this.closeTipUser) {
|
||||
this.shadowRoot.getElementById("amountInput").value = "";
|
||||
this.errorMessage = "";
|
||||
this.successMessage = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getLastRef() {
|
||||
let myRef = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
url: `/addresses/lastreference/${this.myAddress.address}`,
|
||||
})
|
||||
return myRef;
|
||||
}
|
||||
|
||||
renderSuccessText() {
|
||||
return html`${translate("chatpage.cchange55")}`
|
||||
}
|
||||
|
||||
renderReceiverText() {
|
||||
return html`${translate("chatpage.cchange54")}`
|
||||
}
|
||||
|
||||
getApiKey() {
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
|
||||
let apiKey = myNode.apiKey;
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
async fetchWalletDetails() {
|
||||
await parentEpml.request('apiCall', {
|
||||
url: `/addresses/balance/${this.myAddress.address}?apiKey=${this.getApiKey()}`,
|
||||
})
|
||||
.then((res) => {
|
||||
if (isNaN(Number(res))) {
|
||||
let snack4string = get("chatpage.cchange48")
|
||||
parentEpml.request('showSnackBar', `${snack4string}`)
|
||||
} else {
|
||||
this.walletBalance = Number(res).toFixed(8);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async sendQort() {
|
||||
const amount = this.shadowRoot.getElementById("amountInput").value;
|
||||
let recipient = this.userName;
|
||||
this.sendMoneyLoading = true;
|
||||
this.btnDisable = true;
|
||||
|
||||
if (parseFloat(amount) + parseFloat(0.001) > parseFloat(this.walletBalance)) {
|
||||
this.sendMoneyLoading = false;
|
||||
this.btnDisable = false;
|
||||
let snack1string = get("chatpage.cchange51");
|
||||
parentEpml.request('showSnackBar', `${snack1string}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (parseFloat(amount) <= 0) {
|
||||
this.sendMoneyLoading = false;
|
||||
this.btnDisable = false;
|
||||
let snack2string = get("chatpage.cchange52");
|
||||
parentEpml.request('showSnackBar', `${snack2string}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (recipient.length === 0) {
|
||||
this.sendMoneyLoading = false;
|
||||
this.btnDisable = false;
|
||||
let snack3string = get("chatpage.cchange53");
|
||||
parentEpml.request('showSnackBar', `${snack3string}`);
|
||||
return false;
|
||||
}
|
||||
|
||||
const validateName = async (receiverName) => {
|
||||
let myRes;
|
||||
let myNameRes = await parentEpml.request('apiCall', {
|
||||
type: 'api',
|
||||
url: `/names/${receiverName}`,
|
||||
})
|
||||
|
||||
if (myNameRes.error === 401) {
|
||||
myRes = false;
|
||||
} else {
|
||||
myRes = myNameRes;
|
||||
}
|
||||
return myRes;
|
||||
}
|
||||
|
||||
const validateAddress = async (receiverAddress) => {
|
||||
let myAddress = await window.parent.validateAddress(receiverAddress);
|
||||
return myAddress;
|
||||
}
|
||||
|
||||
const validateReceiver = async (recipient) => {
|
||||
let lastRef = await this.getLastRef();
|
||||
let isAddress;
|
||||
|
||||
try {
|
||||
isAddress = await validateAddress(recipient);
|
||||
} catch (err) {
|
||||
isAddress = false;
|
||||
}
|
||||
|
||||
if (isAddress) {
|
||||
let myTransaction = await makeTransactionRequest(recipient, lastRef);
|
||||
getTxnRequestResponse(myTransaction);
|
||||
} else {
|
||||
let myNameRes = await validateName(recipient);
|
||||
if (myNameRes !== false) {
|
||||
let myNameAddress = myNameRes.owner
|
||||
let myTransaction = await makeTransactionRequest(myNameAddress, lastRef)
|
||||
getTxnRequestResponse(myTransaction)
|
||||
} else {
|
||||
console.error(this.renderReceiverText())
|
||||
this.errorMessage = this.renderReceiverText();
|
||||
this.sendMoneyLoading = false;
|
||||
this.btnDisable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const getName = async (recipient)=> {
|
||||
try {
|
||||
const getNames = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
url: `/names/address/${recipient}`,
|
||||
});
|
||||
|
||||
if (getNames?.length > 0 ) {
|
||||
return getNames[0].name;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
} catch (error) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
const makeTransactionRequest = async (receiver, lastRef) => {
|
||||
let myReceiver = receiver;
|
||||
let mylastRef = lastRef;
|
||||
let dialogamount = get("transactions.amount");
|
||||
let dialogAddress = get("login.address");
|
||||
let dialogName = get("login.name");
|
||||
let dialogto = get("transactions.to");
|
||||
let recipientName = await getName(myReceiver);
|
||||
let myTxnrequest = await parentEpml.request('transaction', {
|
||||
type: 2,
|
||||
nonce: this.myAddress.nonce,
|
||||
params: {
|
||||
recipient: myReceiver,
|
||||
recipientName: recipientName,
|
||||
amount: amount,
|
||||
lastReference: mylastRef,
|
||||
fee: 0.001,
|
||||
dialogamount: dialogamount,
|
||||
dialogto: dialogto,
|
||||
dialogAddress,
|
||||
dialogName
|
||||
},
|
||||
})
|
||||
return myTxnrequest;
|
||||
}
|
||||
|
||||
const getTxnRequestResponse = (txnResponse) => {
|
||||
if (txnResponse.success === false && txnResponse.message) {
|
||||
this.errorMessage = txnResponse.message;
|
||||
this.sendMoneyLoading = false;
|
||||
this.btnDisable = false;
|
||||
throw new Error(txnResponse);
|
||||
} else if (txnResponse.success === true && !txnResponse.data.error) {
|
||||
this.shadowRoot.getElementById('amountInput').value = '';
|
||||
this.errorMessage = '';
|
||||
this.successMessage = this.renderSuccessText();
|
||||
this.sendMoneyLoading = false;
|
||||
this.btnDisable = false;
|
||||
setTimeout(() => {
|
||||
this.setOpenTipUser(false);
|
||||
this.successMessage = "";
|
||||
}, 3000);
|
||||
} else {
|
||||
this.errorMessage = txnResponse.data.message;
|
||||
this.sendMoneyLoading = false;
|
||||
this.btnDisable = false;
|
||||
throw new Error(txnResponse);
|
||||
}
|
||||
}
|
||||
validateReceiver(recipient);
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="tip-user-header">
|
||||
<img src="/img/qort.png" width="32" height="32">
|
||||
<p class="tip-user-header-font">${translate("chatpage.cchange43")} ${this.userName}</p>
|
||||
</div>
|
||||
<div class="tip-user-body">
|
||||
<p class="tip-available">${translate("chatpage.cchange47")}: ${this.walletBalance} QORT</p>
|
||||
<input id="amountInput" class="tip-input" type="number" placeholder="${translate("chatpage.cchange46")}" />
|
||||
<p class="tip-available">${translate("chatpage.cchange49")}: 0.001 QORT</p>
|
||||
${this.sendMoneyLoading ?
|
||||
html`
|
||||
<paper-progress indeterminate style="width: 100%; margin: 4px;">
|
||||
</paper-progress>`
|
||||
: html`
|
||||
<div style=${"text-align: center;"}>
|
||||
<vaadin-button
|
||||
?disabled=${this.btnDisable}
|
||||
theme="primary medium"
|
||||
style="width: 100%; cursor: pointer"
|
||||
@click=${() => this.sendQort()}>
|
||||
<vaadin-icon icon="vaadin:arrow-forward" slot="prefix"></vaadin-icon>
|
||||
${translate("chatpage.cchange50")} QORT
|
||||
</vaadin-button>
|
||||
</div>
|
||||
`}
|
||||
|
||||
${this.successMessage ?
|
||||
html`
|
||||
<p class="success-msg">
|
||||
${this.successMessage}
|
||||
</p>
|
||||
`
|
||||
: this.errorMessage ?
|
||||
html`
|
||||
<p class="error-msg">
|
||||
${this.errorMessage}
|
||||
</p>
|
||||
`
|
||||
: null}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('tip-user', TipUser);
|
@ -0,0 +1,69 @@
|
||||
import { css } from 'lit'
|
||||
|
||||
export const userInfoStyles = css`
|
||||
.user-info-header {
|
||||
font-family: Montserrat, sans-serif;
|
||||
text-align: center;
|
||||
font-size: 28px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
margin-bottom: 10px;
|
||||
padding: 10px 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.avatar-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.user-info-avatar {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.user-info-no-avatar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-transform: capitalize;
|
||||
font-size: 50px;
|
||||
font-family: Roboto, sans-serif;
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius:50%;
|
||||
background: var(--chatHeadBg);
|
||||
color: var(--chatHeadText);
|
||||
}
|
||||
|
||||
.send-message-button {
|
||||
font-family: Roboto, sans-serif;
|
||||
letter-spacing: 0.3px;
|
||||
font-weight: 300;
|
||||
padding: 8px 5px;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
color: var(--mdc-theme-primary);
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.send-message-button:hover {
|
||||
cursor: pointer;
|
||||
background-color: #03a8f485;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 5px;
|
||||
color: #676b71;
|
||||
width: 14px;
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.close-icon:hover {
|
||||
cursor: pointer;
|
||||
color: #494c50;
|
||||
}
|
||||
`
|
119
qortal-ui-plugins/plugins/core/components/UserInfo/UserInfo.js
Normal file
@ -0,0 +1,119 @@
|
||||
import { LitElement, html } from 'lit';
|
||||
import { render } from 'lit/html.js';
|
||||
import { translate } from 'lit-translate';
|
||||
import { userInfoStyles } from './UserInfo-css.js';
|
||||
import { Epml } from '../../../../epml';
|
||||
import '@vaadin/button';
|
||||
import '@polymer/paper-progress/paper-progress.js';
|
||||
import { cropAddress } from '../../../utils/cropAddress.js';
|
||||
|
||||
export class UserInfo extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
setOpenUserInfo: { attribute: false },
|
||||
setOpenTipUser: { attribute: false },
|
||||
setOpenPrivateMessage: { attribute: false },
|
||||
userName: { type: String },
|
||||
selectedHead: { type: Object },
|
||||
isImageLoaded: { type: Boolean }
|
||||
}
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.isImageLoaded = false
|
||||
this.selectedHead = {}
|
||||
this.imageFetches = 0
|
||||
}
|
||||
|
||||
static styles = [userInfoStyles]
|
||||
|
||||
createImage(imageUrl) {
|
||||
const imageHTMLRes = new Image();
|
||||
imageHTMLRes.src = imageUrl;
|
||||
imageHTMLRes.classList.add("user-info-avatar");
|
||||
imageHTMLRes.onload = () => {
|
||||
this.isImageLoaded = true;
|
||||
}
|
||||
imageHTMLRes.onerror = () => {
|
||||
if (this.imageFetches < 4) {
|
||||
setTimeout(() => {
|
||||
this.imageFetches = this.imageFetches + 1;
|
||||
imageHTMLRes.src = imageUrl;
|
||||
}, 500);
|
||||
} else {
|
||||
this.isImageLoaded = false
|
||||
}
|
||||
};
|
||||
return imageHTMLRes;
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
let avatarImg = "";
|
||||
if (this.selectedHead && this.selectedHead.name) {
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
|
||||
const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${this.selectedHead.name}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`;
|
||||
avatarImg = this.createImage(avatarUrl);
|
||||
}
|
||||
return html`
|
||||
<div style=${"position: relative;"}>
|
||||
<vaadin-icon
|
||||
class="close-icon"
|
||||
icon="vaadin:close-big"
|
||||
slot="icon"
|
||||
@click=${() => {
|
||||
this.setOpenUserInfo(false)
|
||||
}}>
|
||||
</vaadin-icon>
|
||||
${this.isImageLoaded ?
|
||||
html`
|
||||
<div class="avatar-container">
|
||||
${avatarImg}
|
||||
</div>` :
|
||||
html``}
|
||||
${!this.isImageLoaded && this.selectedHead && this.selectedHead.name ?
|
||||
html`
|
||||
<div class="avatar-container">
|
||||
<div class="user-info-no-avatar">
|
||||
${this.selectedHead.name.charAt(0)}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
: ""}
|
||||
${!this.isImageLoaded && this.selectedHead && !this.selectedHead.name ?
|
||||
html`
|
||||
<div class="avatar-container">
|
||||
<img src="/img/qortal-chat-logo.png" alt="avatar" />
|
||||
</div>`
|
||||
: ""}
|
||||
<div class="user-info-header">
|
||||
${this.selectedHead && this.selectedHead.name ? this.selectedHead.name : this.selectedHead ? cropAddress(this.selectedHead.address) : null}
|
||||
</div>
|
||||
<div
|
||||
class="send-message-button"
|
||||
@click="${() => {
|
||||
this.setOpenPrivateMessage({
|
||||
name: this.userName,
|
||||
open: true
|
||||
})
|
||||
this.setOpenUserInfo(false);
|
||||
}
|
||||
}">
|
||||
${translate("chatpage.cchange58")}
|
||||
</div>
|
||||
<div
|
||||
style=${"margin-top: 5px;"}
|
||||
class="send-message-button"
|
||||
@click=${() => {
|
||||
this.setOpenTipUser(true);
|
||||
this.setOpenUserInfo(false);
|
||||
}}>
|
||||
${translate("chatpage.cchange59")}
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
customElements.define('user-info', UserInfo);
|
@ -0,0 +1,57 @@
|
||||
import { css } from 'lit'
|
||||
|
||||
export const wrapperModalStyles = css`
|
||||
.backdrop {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background: rgb(186 186 186 / 26%);
|
||||
overflow: hidden;
|
||||
animation: backdrop_blur cubic-bezier(0.22, 1, 0.36, 1) 1s forwards;
|
||||
z-index: 50
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
height: auto;
|
||||
position: fixed;
|
||||
box-shadow: rgb(60 64 67 / 30%) 0px 1px 2px 0px, rgb(60 64 67 / 15%) 0px 2px 6px 2px;
|
||||
width: 500px;
|
||||
z-index: 5;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 15px;
|
||||
background-color: var(--white);
|
||||
left: 50%;
|
||||
top: 0px;
|
||||
transform: translate(-50%, 10%);
|
||||
border-radius: 12px;
|
||||
overflow-y: auto;
|
||||
animation: 1s cubic-bezier(0.22, 1, 0.36, 1) 0s 1 normal forwards running modal_transition;
|
||||
max-height: 80%;
|
||||
z-index: 60
|
||||
}
|
||||
|
||||
@keyframes backdrop_blur {
|
||||
0% {
|
||||
backdrop-filter: blur(0px);
|
||||
background: transparent;
|
||||
}
|
||||
100% {
|
||||
backdrop-filter: blur(5px);
|
||||
background: rgb(186 186 186 / 26%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes modal_transition {
|
||||
0% {
|
||||
visibility: hidden;
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
visibility: visible;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
`
|
33
qortal-ui-plugins/plugins/core/components/WrapperModal.js
Normal file
@ -0,0 +1,33 @@
|
||||
import { LitElement, html } from 'lit';
|
||||
import { render } from 'lit/html.js';
|
||||
import { wrapperModalStyles } from './WrapperModal-css.js'
|
||||
|
||||
export class WrapperModal extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
customStyle: {type: String},
|
||||
onClickFunc: { attribute: false },
|
||||
zIndex: {type: Number}
|
||||
}
|
||||
}
|
||||
|
||||
static styles = [wrapperModalStyles]
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div>
|
||||
<div
|
||||
style="z-index: ${this.zIndex || 50}"
|
||||
class="backdrop"
|
||||
@click=${() => {
|
||||
this.onClickFunc();
|
||||
}}>
|
||||
</div>
|
||||
<div class="modal-body" style=${this.customStyle ? this.customStyle : ""}>
|
||||
<slot></slot>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
customElements.define('wrapper-modal', WrapperModal);
|
@ -0,0 +1,82 @@
|
||||
import { Sha256 } from 'asmcrypto.js'
|
||||
|
||||
|
||||
function sbrk(size, heap){
|
||||
let brk = 512 * 1024 // stack top
|
||||
let old = brk
|
||||
brk += size
|
||||
|
||||
if (brk > heap.length)
|
||||
throw new Error('heap exhausted')
|
||||
|
||||
return old
|
||||
}
|
||||
|
||||
|
||||
|
||||
self.addEventListener('message', async e => {
|
||||
const response = await computePow(e.data.chatBytes, e.data.path, e.data.difficulty)
|
||||
postMessage(response)
|
||||
|
||||
})
|
||||
|
||||
|
||||
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 })
|
||||
const heap = new Uint8Array(memory.buffer)
|
||||
|
||||
|
||||
|
||||
const computePow = async (chatBytes, path, difficulty) => {
|
||||
|
||||
let response = null
|
||||
|
||||
await new Promise((resolve, reject)=> {
|
||||
|
||||
const _chatBytesArray = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; });
|
||||
const chatBytesArray = new Uint8Array(_chatBytesArray);
|
||||
const chatBytesHash = new Sha256().process(chatBytesArray).finish().result;
|
||||
const hashPtr = sbrk(32, heap);
|
||||
const hashAry = new Uint8Array(memory.buffer, hashPtr, 32);
|
||||
hashAry.set(chatBytesHash);
|
||||
|
||||
|
||||
const workBufferLength = 8 * 1024 * 1024;
|
||||
const workBufferPtr = sbrk(workBufferLength, heap);
|
||||
|
||||
|
||||
|
||||
const importObject = {
|
||||
env: {
|
||||
memory: memory
|
||||
},
|
||||
};
|
||||
|
||||
function loadWebAssembly(filename, imports) {
|
||||
// Fetch the file and compile it
|
||||
return fetch(filename)
|
||||
.then(response => response.arrayBuffer())
|
||||
.then(buffer => WebAssembly.compile(buffer))
|
||||
.then(module => {
|
||||
|
||||
// Create the instance.
|
||||
return new WebAssembly.Instance(module, importObject);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
loadWebAssembly(path)
|
||||
.then(wasmModule => {
|
||||
response = {
|
||||
nonce : wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty),
|
||||
chatBytesArray
|
||||
}
|
||||
|
||||
resolve()
|
||||
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
@ -0,0 +1,92 @@
|
||||
import { Sha256 } from 'asmcrypto.js'
|
||||
|
||||
|
||||
|
||||
function sbrk(size, heap){
|
||||
let brk = 512 * 1024 // stack top
|
||||
let old = brk
|
||||
brk += size
|
||||
|
||||
if (brk > heap.length)
|
||||
throw new Error('heap exhausted')
|
||||
|
||||
return old
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
self.addEventListener('message', async e => {
|
||||
const response = await computePow(e.data.convertedBytes, e.data.path)
|
||||
postMessage(response)
|
||||
|
||||
})
|
||||
|
||||
|
||||
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 })
|
||||
const heap = new Uint8Array(memory.buffer)
|
||||
|
||||
|
||||
|
||||
const computePow = async (convertedBytes, path) => {
|
||||
|
||||
|
||||
let response = null
|
||||
|
||||
await new Promise((resolve, reject)=> {
|
||||
|
||||
const _convertedBytesArray = Object.keys(convertedBytes).map(
|
||||
function (key) {
|
||||
return convertedBytes[key]
|
||||
}
|
||||
)
|
||||
const convertedBytesArray = new Uint8Array(_convertedBytesArray)
|
||||
const convertedBytesHash = new Sha256()
|
||||
.process(convertedBytesArray)
|
||||
.finish().result
|
||||
const hashPtr = sbrk(32, heap)
|
||||
const hashAry = new Uint8Array(
|
||||
memory.buffer,
|
||||
hashPtr,
|
||||
32
|
||||
)
|
||||
|
||||
hashAry.set(convertedBytesHash)
|
||||
const difficulty = 14
|
||||
const workBufferLength = 8 * 1024 * 1024
|
||||
const workBufferPtr = sbrk(
|
||||
workBufferLength,
|
||||
heap
|
||||
)
|
||||
|
||||
const importObject = {
|
||||
env: {
|
||||
memory: memory
|
||||
},
|
||||
};
|
||||
|
||||
function loadWebAssembly(filename, imports) {
|
||||
return fetch(filename)
|
||||
.then(response => response.arrayBuffer())
|
||||
.then(buffer => WebAssembly.compile(buffer))
|
||||
.then(module => {
|
||||
return new WebAssembly.Instance(module, importObject);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
loadWebAssembly(path)
|
||||
.then(wasmModule => {
|
||||
response = {
|
||||
nonce : wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty),
|
||||
|
||||
}
|
||||
resolve()
|
||||
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
@ -1820,6 +1820,7 @@ class GroupManagement extends LitElement {
|
||||
setTimeout(getGroupInvites, 1)
|
||||
configLoaded = true
|
||||
}
|
||||
console.log('parse', JSON.parse(c))
|
||||
this.config = JSON.parse(c)
|
||||
})
|
||||
parentEpml.subscribe('copy_menu_switch', async value => {
|
||||
|
@ -0,0 +1,82 @@
|
||||
import { Sha256 } from 'asmcrypto.js'
|
||||
|
||||
|
||||
function sbrk(size, heap){
|
||||
let brk = 512 * 1024 // stack top
|
||||
let old = brk
|
||||
brk += size
|
||||
|
||||
if (brk > heap.length)
|
||||
throw new Error('heap exhausted')
|
||||
|
||||
return old
|
||||
}
|
||||
|
||||
|
||||
|
||||
self.addEventListener('message', async e => {
|
||||
const response = await computePow(e.data.chatBytes, e.data.path, e.data.difficulty)
|
||||
postMessage(response)
|
||||
|
||||
})
|
||||
|
||||
|
||||
const memory = new WebAssembly.Memory({ initial: 256, maximum: 256 })
|
||||
const heap = new Uint8Array(memory.buffer)
|
||||
|
||||
|
||||
|
||||
const computePow = async (chatBytes, path, difficulty) => {
|
||||
|
||||
let response = null
|
||||
|
||||
await new Promise((resolve, reject)=> {
|
||||
|
||||
const _chatBytesArray = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; });
|
||||
const chatBytesArray = new Uint8Array(_chatBytesArray);
|
||||
const chatBytesHash = new Sha256().process(chatBytesArray).finish().result;
|
||||
const hashPtr = sbrk(32, heap);
|
||||
const hashAry = new Uint8Array(memory.buffer, hashPtr, 32);
|
||||
hashAry.set(chatBytesHash);
|
||||
|
||||
|
||||
const workBufferLength = 8 * 1024 * 1024;
|
||||
const workBufferPtr = sbrk(workBufferLength, heap);
|
||||
|
||||
|
||||
|
||||
const importObject = {
|
||||
env: {
|
||||
memory: memory
|
||||
},
|
||||
};
|
||||
|
||||
function loadWebAssembly(filename, imports) {
|
||||
// Fetch the file and compile it
|
||||
return fetch(filename)
|
||||
.then(response => response.arrayBuffer())
|
||||
.then(buffer => WebAssembly.compile(buffer))
|
||||
.then(module => {
|
||||
|
||||
// Create the instance.
|
||||
return new WebAssembly.Instance(module, importObject);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
loadWebAssembly(path)
|
||||
.then(wasmModule => {
|
||||
response = {
|
||||
nonce : wasmModule.exports.compute2(hashPtr, workBufferPtr, workBufferLength, difficulty),
|
||||
chatBytesArray
|
||||
}
|
||||
|
||||
resolve()
|
||||
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
|
||||
return response
|
||||
}
|
@ -0,0 +1,479 @@
|
||||
import { css } from 'lit'
|
||||
|
||||
export const qchatStyles = css`
|
||||
* {
|
||||
--mdc-theme-primary: rgb(3, 169, 244);
|
||||
--mdc-theme-secondary: var(--mdc-theme-primary);
|
||||
--paper-input-container-focus-color: var(--mdc-theme-primary);
|
||||
--mdc-theme-surface: var(--white);
|
||||
--mdc-dialog-content-ink-color: var(--black);
|
||||
--lumo-primary-text-color: rgb(0, 167, 245);
|
||||
--lumo-primary-color-50pct: rgba(0, 167, 245, 0.5);
|
||||
--lumo-primary-color-10pct: rgba(0, 167, 245, 0.1);
|
||||
--lumo-primary-color: hsl(199, 100%, 48%);
|
||||
--lumo-base-color: var(--white);
|
||||
--lumo-body-text-color: var(--black);
|
||||
--_lumo-grid-border-color: var(--border);
|
||||
--_lumo-grid-secondary-border-color: var(--border2);
|
||||
--mdc-dialog-min-width: 750px;
|
||||
}
|
||||
|
||||
paper-spinner-lite {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
--paper-spinner-color: var(--mdc-theme-primary);
|
||||
--paper-spinner-stroke-width: 2px;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.container {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
.people-list {
|
||||
width: 20vw;
|
||||
float: left;
|
||||
height: 100vh;
|
||||
overflow-y: hidden;
|
||||
border-right: 3px #ddd solid;
|
||||
}
|
||||
|
||||
.people-list .blockedusers {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 20vw;
|
||||
background: var(--white);
|
||||
border-top: 1px solid var(--border);
|
||||
border-right: 3px #ddd solid;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 15px;
|
||||
flex-direction: column;
|
||||
padding: 5px 30px 0 30px;
|
||||
}
|
||||
|
||||
.groups-button-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.groups-button {
|
||||
width: 100%;
|
||||
background-color: rgb(116, 69, 240);
|
||||
border: none;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
font-family: 'Roboto';
|
||||
letter-spacing: 0.8px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 5px;
|
||||
gap: 10px;
|
||||
padding: 5px 8px;
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
|
||||
.groups-button-notif {
|
||||
position: absolute;
|
||||
top: -10px;
|
||||
right: -8px;
|
||||
width: 25px;
|
||||
border-radius: 50%;
|
||||
height: 25px;
|
||||
font-weight: bold;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-family: Montserrat, sans-serif;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
color: black;
|
||||
background-color: rgb(51, 213, 0);
|
||||
user-select: none;
|
||||
transition: all 0.3s ease-in-out 0s;
|
||||
}
|
||||
|
||||
.groups-button-notif:hover {
|
||||
cursor: auto;
|
||||
box-shadow: rgba(99, 99, 99, 0.2) 0px 2px 8px 0px;
|
||||
}
|
||||
|
||||
.groups-button-notif:hover + .groups-button-notif-number {
|
||||
display: block;
|
||||
opacity: 1;
|
||||
animation: fadeIn 0.6s;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
top: -10px;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
top: -60px;
|
||||
}
|
||||
}
|
||||
|
||||
.groups-button-notif-number {
|
||||
position: absolute;
|
||||
transform: translateX(-50%);
|
||||
left: 50%;
|
||||
width: 150px;
|
||||
text-align: center;
|
||||
border-radius: 3px;
|
||||
padding: 5px 10px;
|
||||
background-color: white;
|
||||
color: black;
|
||||
font-family: Roboto, sans-serif;
|
||||
letter-spacing: 0.3px;
|
||||
font-weight: 300;
|
||||
display: none;
|
||||
opacity: 0;
|
||||
top: -60px;
|
||||
box-shadow: rgb(216 216 216 / 25%) 0px 6px 12px -2px, rgb(0 0 0 / 30%) 0px 3px 7px -3px;
|
||||
}
|
||||
|
||||
.groups-button:hover {
|
||||
cursor: pointer;
|
||||
filter: brightness(120%);
|
||||
}
|
||||
|
||||
.people-list .search {
|
||||
padding-top: 20px;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.center {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
padding-top: 12px;
|
||||
left: 50%;
|
||||
-ms-transform: translateX(-50%);
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.people-list .create-chat {
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
display: inline-block;
|
||||
padding: 14px;
|
||||
color: #fff;
|
||||
background: var(--tradehead);
|
||||
width: 100%;
|
||||
font-size: 15px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.people-list .create-chat:hover {
|
||||
opacity: .8;
|
||||
box-shadow: 0 3px 5px rgba(0, 0, 0, .2);
|
||||
}
|
||||
|
||||
.people-list ul {
|
||||
padding: 0px 0px 60px 0px;
|
||||
height: 85vh;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
.people-list ul::-webkit-scrollbar-track {
|
||||
background-color: whitesmoke;
|
||||
border-radius: 7px;
|
||||
}
|
||||
|
||||
.people-list ul::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
border-radius: 7px;
|
||||
background-color: whitesmoke;
|
||||
}
|
||||
|
||||
.people-list ul::-webkit-scrollbar-thumb {
|
||||
background-color: rgb(180, 176, 176);
|
||||
border-radius: 7px;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.chat {
|
||||
width: 80vw;
|
||||
height: 100vh;
|
||||
float: left;
|
||||
background: var(--white);
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom-right-radius: 5px;
|
||||
color: #434651;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chat .new-message-bar {
|
||||
display: flex;
|
||||
flex: 0 1 auto;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0px 25px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
top: 0;
|
||||
position: absolute;
|
||||
left: 20vw;
|
||||
right: 0;
|
||||
z-index: 5;
|
||||
background: var(--tradehead);
|
||||
color: var(--white);
|
||||
border-radius: 0 0 8px 8px;
|
||||
min-height: 25px;
|
||||
transition: opacity .15s;
|
||||
text-transform: capitalize;
|
||||
opacity: .85;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chat .new-message-bar:hover {
|
||||
opacity: .75;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 3px 7px rgba(0, 0, 0, .2);
|
||||
}
|
||||
|
||||
.hide-new-message-bar {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.chat .chat-history {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 100%;
|
||||
left: 20vw;
|
||||
border-bottom: 2px solid var(--white);
|
||||
overflow-y: hidden;
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chat .chat-message {
|
||||
padding: 10px;
|
||||
height: 10%;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.chat .chat-message textarea {
|
||||
width: 90%;
|
||||
border: none;
|
||||
font-size: 16px;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.chat .chat-message button {
|
||||
float: right;
|
||||
color: #94c2ed;
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
background: #f2f5f8;
|
||||
padding: 10px;
|
||||
margin-top: 4px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.chat .chat-message button:hover {
|
||||
color: #75b1e8;
|
||||
}
|
||||
|
||||
.online,
|
||||
.offline,
|
||||
.me {
|
||||
margin-right: 3px;
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
visibility: hidden;
|
||||
display: block;
|
||||
font-size: 0;
|
||||
content: " ";
|
||||
clear: both;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.red {
|
||||
--mdc-theme-primary: red;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin:0;
|
||||
}
|
||||
|
||||
h2, h3, h4, h5 {
|
||||
color: var(--black);
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: hidden !important;
|
||||
visibility: none !important;
|
||||
}
|
||||
|
||||
.details {
|
||||
display: flex;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight:600;
|
||||
font-size:12px;
|
||||
line-height: 32px;
|
||||
opacity: 0.66;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
width: 100%;
|
||||
border: none;
|
||||
display: inline-block;
|
||||
font-size: 16px;
|
||||
padding: 10px 20px;
|
||||
border-radius: 5px;
|
||||
height: 120px;
|
||||
resize: none;
|
||||
background: #eee;
|
||||
}
|
||||
|
||||
.dialog-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 0 10px;
|
||||
gap: 10px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.dialog-header {
|
||||
color: var(--chat-bubble-msg-color);
|
||||
}
|
||||
|
||||
.dialog-subheader {
|
||||
color: var(--chat-bubble-msg-color);
|
||||
}
|
||||
|
||||
.modal-button-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.modal-button {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 16px;
|
||||
color: var(--mdc-theme-primary);
|
||||
background-color: transparent;
|
||||
padding: 8px 10px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.modal-button-red {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 16px;
|
||||
color: #F44336;
|
||||
background-color: transparent;
|
||||
padding: 8px 10px;
|
||||
border-radius: 5px;
|
||||
border: none;
|
||||
transition: all 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.modal-button-red:hover {
|
||||
cursor: pointer;
|
||||
background-color: #f4433663;
|
||||
}
|
||||
|
||||
.modal-button:hover {
|
||||
cursor: pointer;
|
||||
background-color: #03a8f475;
|
||||
}
|
||||
|
||||
.name-input {
|
||||
width: 100%;
|
||||
outline: 0;
|
||||
border-width: 0 0 2px;
|
||||
border-color: var(--mdc-theme-primary);
|
||||
background-color: transparent;
|
||||
padding: 10px;
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 15px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.name-input::selection {
|
||||
background-color: var(--mdc-theme-primary);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.name-input::placeholder {
|
||||
opacity: 0.9;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.search-field {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
color: var(--chat-bubble-msg-color);
|
||||
transition: all 0.3s ease-in-out;
|
||||
background: none;
|
||||
border-radius: 50%;
|
||||
padding: 6px 3px;
|
||||
font-size: 21px;
|
||||
}
|
||||
|
||||
.search-icon:hover {
|
||||
cursor: pointer;
|
||||
background: #d7d7d75c;
|
||||
}
|
||||
|
||||
.search-results-div {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 25px;
|
||||
}
|
||||
|
||||
.user-verified {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
color: #04aa2e;
|
||||
font-size: 13px;
|
||||
}
|
||||
`
|
@ -272,7 +272,7 @@ class NameRegistration extends LitElement {
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
const url = `${nodeUrl}/arbitrary/THUMBNAIL/${name}/qortal_avatar?async=true&apiKey=${this.getApiKey()}`;
|
||||
return html`<img src="${url}" onerror="this.onerror=null; this.src='/img/incognito.png';">`
|
||||
return html`<img src="${url}" onerror="this.onerror=null; this.src='/img/qortal-chat-logo.png';">`
|
||||
}
|
||||
|
||||
renderAvatarButton(nameObj) {
|
||||
|
@ -703,7 +703,7 @@ class Websites extends LitElement {
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
const url = `${nodeUrl}/arbitrary/THUMBNAIL/${name}/qortal_avatar?async=true&apiKey=${this.getApiKey()}`
|
||||
return html`<a class="visitSite" href="browser/index.html?name=${name}&service=${this.service}"><img src="${url}" onerror="this.src='/img/incognito.png';"></a>`
|
||||
return html`<a class="visitSite" href="browser/index.html?name=${name}&service=${this.service}"><img src="${url}" onerror="this.src='/img/qortal-chat-logo.png';"></a>`
|
||||
}
|
||||
|
||||
renderRelayModeText() {
|
||||
|
@ -534,7 +534,7 @@ class SponsorshipList extends LitElement {
|
||||
${sponsorship?.name ? html`
|
||||
<img src=${sponsorship.url}
|
||||
class="avatar-img"
|
||||
onerror="this.src='/img/incognito.png'"
|
||||
onerror="this.src='/img/qortal-chat-logo.png'"
|
||||
/>
|
||||
` : ''}
|
||||
${sponsorship?.name || sponsorship.address}
|
||||
|
8
qortal-ui-plugins/plugins/utils/cropAddress.js
Normal file
@ -0,0 +1,8 @@
|
||||
export function cropAddress(string = "", range = 5) {
|
||||
const [start, end] = [
|
||||
string?.substring(0, range),
|
||||
string?.substring(string?.length - range, string?.length),
|
||||
//
|
||||
];
|
||||
return start + "..." + end;
|
||||
}
|
21
qortal-ui-plugins/plugins/utils/getUserNameFromAddress.js
Normal file
@ -0,0 +1,21 @@
|
||||
import { Epml } from '../../epml.js';
|
||||
import { cropAddress } from './cropAddress.js';
|
||||
|
||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
||||
|
||||
export const getUserNameFromAddress = async (address) => {
|
||||
try {
|
||||
const getNames = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
url: `/names/address/${address}`,
|
||||
});
|
||||
|
||||
if (Array.isArray(getNames) && getNames.length > 0 ) {
|
||||
return getNames[0].name;
|
||||
} else {
|
||||
return address;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
143
qortal-ui-plugins/plugins/utils/publish-image.js
Normal file
@ -0,0 +1,143 @@
|
||||
const getApiKey = () => {
|
||||
const myNode =
|
||||
window.parent.reduxStore.getState().app.nodeConfig.knownNodes[
|
||||
window.parent.reduxStore.getState().app.nodeConfig.node
|
||||
]
|
||||
let apiKey = myNode.apiKey
|
||||
return apiKey
|
||||
}
|
||||
|
||||
export const publishData = async ({
|
||||
registeredName,
|
||||
path,
|
||||
file,
|
||||
service,
|
||||
identifier,
|
||||
parentEpml,
|
||||
uploadType,
|
||||
selectedAddress,
|
||||
worker
|
||||
}) => {
|
||||
const validateName = async (receiverName) => {
|
||||
let nameRes = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
url: `/names/${receiverName}`,
|
||||
})
|
||||
|
||||
return nameRes
|
||||
}
|
||||
|
||||
const convertBytesForSigning = async (transactionBytesBase58) => {
|
||||
let convertedBytes = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
method: "POST",
|
||||
url: `/transactions/convert`,
|
||||
body: `${transactionBytesBase58}`,
|
||||
})
|
||||
return convertedBytes
|
||||
}
|
||||
|
||||
const signAndProcess = async (transactionBytesBase58) => {
|
||||
let convertedBytesBase58 = await convertBytesForSigning(
|
||||
transactionBytesBase58
|
||||
)
|
||||
if (convertedBytesBase58.error) {
|
||||
return
|
||||
}
|
||||
|
||||
const convertedBytes =
|
||||
window.parent.Base58.decode(convertedBytesBase58)
|
||||
let nonce = null
|
||||
const computPath =window.parent.location.origin + '/memory-pow/memory-pow.wasm.full'
|
||||
await new Promise((res, rej) => {
|
||||
|
||||
worker.postMessage({convertedBytes, path: computPath});
|
||||
|
||||
worker.onmessage = e => {
|
||||
|
||||
worker.terminate()
|
||||
|
||||
nonce = e.data.nonce
|
||||
res()
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
let response = await parentEpml.request("sign_arbitrary", {
|
||||
nonce: selectedAddress.nonce,
|
||||
arbitraryBytesBase58: transactionBytesBase58,
|
||||
arbitraryBytesForSigningBase58: convertedBytesBase58,
|
||||
arbitraryNonce: nonce,
|
||||
})
|
||||
let myResponse = { error: "" }
|
||||
if (response === false) {
|
||||
return
|
||||
} else {
|
||||
myResponse = response
|
||||
}
|
||||
|
||||
return myResponse
|
||||
}
|
||||
|
||||
const validate = async () => {
|
||||
let validNameRes = await validateName(registeredName)
|
||||
if (validNameRes.error) {
|
||||
return
|
||||
}
|
||||
let transactionBytes = await uploadData(registeredName, path, file)
|
||||
if (transactionBytes.error) {
|
||||
return
|
||||
} else if (
|
||||
transactionBytes.includes("Error 500 Internal Server Error")
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
let signAndProcessRes = await signAndProcess(transactionBytes)
|
||||
if (signAndProcessRes.error) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const uploadData = async (registeredName, path, file) => {
|
||||
if (identifier != null && identifier.trim().length > 0) {
|
||||
let postBody = path
|
||||
let urlSuffix = ""
|
||||
if (file != null) {
|
||||
// If we're sending zipped data, make sure to use the /zip version of the POST /arbitrary/* API
|
||||
if (uploadType === "zip") {
|
||||
urlSuffix = "/zip"
|
||||
}
|
||||
// If we're sending file data, use the /base64 version of the POST /arbitrary/* API
|
||||
else if (uploadType === "file") {
|
||||
urlSuffix = "/base64"
|
||||
}
|
||||
|
||||
// Base64 encode the file to work around compatibility issues between javascript and java byte arrays
|
||||
let fileBuffer = new Uint8Array(await file.arrayBuffer())
|
||||
postBody = Buffer.from(fileBuffer).toString("base64")
|
||||
}
|
||||
|
||||
|
||||
|
||||
let uploadDataUrl = `/arbitrary/${service}/${registeredName}${urlSuffix}?apiKey=${getApiKey()}`
|
||||
if (identifier != null && identifier.trim().length > 0) {
|
||||
uploadDataUrl = `/arbitrary/${service}/${registeredName}/${identifier}${urlSuffix}?apiKey=${getApiKey()}`
|
||||
}
|
||||
let uploadDataRes = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
method: "POST",
|
||||
url: `${uploadDataUrl}`,
|
||||
body: `${postBody}`,
|
||||
})
|
||||
return uploadDataRes
|
||||
}
|
||||
}
|
||||
try {
|
||||
await validate()
|
||||
} catch (error) {
|
||||
throw new Error(error.message)
|
||||
}
|
||||
|
||||
}
|
92
qortal-ui-plugins/plugins/utils/replace-messages-edited.js
Normal file
@ -0,0 +1,92 @@
|
||||
export const replaceMessagesEdited = async ({
|
||||
decodedMessages,
|
||||
parentEpml,
|
||||
isReceipient,
|
||||
decodeMessageFunc,
|
||||
_publicKey
|
||||
}) => {
|
||||
const findNewMessages = decodedMessages.map(async (msg) => {
|
||||
let msgItem = msg
|
||||
try {
|
||||
let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}`
|
||||
if (!isReceipient) {
|
||||
msgQuery = `&txGroupId=${msg.txGroupId}`
|
||||
}
|
||||
const response = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
url: `/chat/messages?chatreference=${msg.reference}&reverse=true${msgQuery}`,
|
||||
})
|
||||
|
||||
if (response && Array.isArray(response) && response.length !== 0) {
|
||||
let responseItem = { ...response[0] }
|
||||
const decodeResponseItem = decodeMessageFunc(responseItem, isReceipient, _publicKey)
|
||||
delete decodeResponseItem.timestamp
|
||||
msgItem = {
|
||||
...msg,
|
||||
...decodeResponseItem,
|
||||
editedTimestamp: response[0].timestamp,
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
return msgItem
|
||||
})
|
||||
const updateMessages = await Promise.all(findNewMessages)
|
||||
const findNewMessages2 = updateMessages.map(async (msg) => {
|
||||
let parsedMessageObj = msg
|
||||
try {
|
||||
parsedMessageObj = JSON.parse(msg.decodedMessage)
|
||||
} catch (error) {
|
||||
console.log('error')
|
||||
return msg
|
||||
}
|
||||
let msgItem = msg
|
||||
try {
|
||||
let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}`
|
||||
if (!isReceipient) {
|
||||
msgQuery = `&txGroupId=${msg.txGroupId}`
|
||||
}
|
||||
if (parsedMessageObj.repliedTo) {
|
||||
const response = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
url: `/chat/messages?chatreference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}`,
|
||||
})
|
||||
if (
|
||||
response &&
|
||||
Array.isArray(response) &&
|
||||
response.length !== 0
|
||||
) {
|
||||
msgItem = {
|
||||
...msg,
|
||||
repliedToData: decodeMessageFunc(response[0], isReceipient, _publicKey),
|
||||
}
|
||||
} else {
|
||||
const response2 = await parentEpml.request("apiCall", {
|
||||
type: "api",
|
||||
url: `/chat/messages?reference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}`,
|
||||
})
|
||||
|
||||
if (
|
||||
response2 &&
|
||||
Array.isArray(response2) &&
|
||||
response2.length !== 0
|
||||
) {
|
||||
msgItem = {
|
||||
...msg,
|
||||
repliedToData: decodeMessageFunc(response2[0], isReceipient, _publicKey),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
return msgItem
|
||||
})
|
||||
const updateMessages2 = await Promise.all(findNewMessages2)
|
||||
|
||||
return updateMessages2
|
||||
}
|