From a3d3d82d9ca11ad7264eef8d9f65e93f076beb56 Mon Sep 17 00:00:00 2001 From: Justin Ferrari <‘justinwesleyferrari@gmail.com’> Date: Fri, 16 Sep 2022 19:53:08 -0500 Subject: [PATCH 001/123] New Messages Refactored --- .../plugins/core/components/ChatPage.js | 58 +++++++------------ 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index 69d18120..f07cebdd 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -314,7 +314,8 @@ class ChatPage extends LitElement { } } - async processMessages(messages, isInitial) { + async processMessages(messages, isInitial) { + if (isInitial) { this.messages = messages.map((eachMessage) => { @@ -330,7 +331,10 @@ class ChatPage extends LitElement { } }) - this._messages = [...this.messages] + this._messages = [...this.messages].sort(function (a, b) { + return a.timestamp + - b.timestamp + }) const adjustMessages = () => { @@ -341,46 +345,32 @@ class ChatPage extends LitElement { // TODO: Determine number of initial messages by screen height... this._messages.length <= 15 ? adjustMessages() : this._initialMessages = this._messages.splice(this._messages.length - 15); - + this.messagesRendered = this._initialMessages - - // try { - // const viewElement = this.shadowRoot.querySelector('chat-scroller') - // console.log({viewElement}) - // // viewElement.scrollTop = this.viewElement.scrollHeight + 50 - // } catch (error) { - // console.error(error) - // } - - + + this.isLoadingMessages = false setTimeout(() => this.downElementObserver(), 500) } else { - let _newMessages = messages.map((eachMessage) => { - if (eachMessage.isText === true) { - let _eachMessage = this.decodeMessage(eachMessage) + messages.forEach((eachMessage) => { + + const _eachMessage = this.decodeMessage(eachMessage) + + + this.renderNewMessage(_eachMessage) - if (this.messageSignature !== eachMessage.signature) { - this.messageSignature = eachMessage.signature - // What are we waiting for, send in the message immediately... - this.renderNewMessage(_eachMessage) - } - return _eachMessage - } else { - let _eachMessage = this.decodeMessage(eachMessage) - if (this.messageSignature !== eachMessage.signature) { - this.messageSignature = eachMessage.signature - this.renderNewMessage(_eachMessage) - } - return _eachMessage - } }) - this.newMessages = this.newMessages.concat(_newMessages) - + + // this.newMessages = this.newMessages.concat(_newMessages) + this.messagesRendered = [...this.messagesRendered].sort(function (a, b) { + return a.timestamp + - b.timestamp + }) + } } @@ -436,10 +426,6 @@ class ChatPage extends LitElement { async renderNewMessage(newMessage) { const viewElement = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement'); - const downObserver = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('downObserver'); - const li = document.createElement('li'); - li.innerHTML = this.chatMessageTemplate(newMessage); - li.id = newMessage.signature; if (newMessage.sender === this.selectedAddress.address) { From 4aa9692e0f54b7d584dcfdef38e63058b8665c92 Mon Sep 17 00:00:00 2001 From: Justin Ferrari <‘justinwesleyferrari@gmail.com’> Date: Sat, 17 Sep 2022 20:33:59 -0500 Subject: [PATCH 002/123] Add reply design to chat --- qortal-ui-core/language/us.json | 1285 +++++++++-------- .../plugins/core/components/ChatPage.js | 45 +- .../core/components/ChatScroller-css.js | 86 +- .../plugins/core/components/ChatScroller.js | 73 +- 4 files changed, 802 insertions(+), 687 deletions(-) diff --git a/qortal-ui-core/language/us.json b/qortal-ui-core/language/us.json index 32f48d13..6c34d8d6 100644 --- a/qortal-ui-core/language/us.json +++ b/qortal-ui-core/language/us.json @@ -1,673 +1,674 @@ { - "selectmenu":{ - "selectlanguage":"Select language", - "languageflag":"us", - "english":"English", - "chinese1":"Chinese (Simplified)", - "chinese2":"Chinese (Traditional)", - "german":"German", - "french":"French", - "polish":"Polish", - "spanish":"Spanish", - "hindi":"Hindi", - "croatian":"Croatian", - "portuguese":"Portuguese", - "hungarian":"Hungarian", - "serbian":"Serbian", - "italian":"Italian", - "russian":"Russian", - "norwegian":"Norwegian", - "romanian":"Romanian", - "korean":"Korean" + "selectmenu": { + "selectlanguage": "Select language", + "languageflag": "us", + "english": "English", + "chinese1": "Chinese (Simplified)", + "chinese2": "Chinese (Traditional)", + "german": "German", + "french": "French", + "polish": "Polish", + "spanish": "Spanish", + "hindi": "Hindi", + "croatian": "Croatian", + "portuguese": "Portuguese", + "hungarian": "Hungarian", + "serbian": "Serbian", + "italian": "Italian", + "russian": "Russian", + "norwegian": "Norwegian", + "romanian": "Romanian", + "korean": "Korean" }, - "sidemenu":{ - "minting":"MINTING", - "mintingdetails":"MINTING DETAILS", - "becomeAMinter":"BECOME A MINTER", - "wallets":"WALLETS", - "tradeportal":"TRADE PORTAL", - "rewardshare":"REWARD SHARE", - "nameregistration":"NAME REGISTRATION", - "websites":"WEBSITES", - "management":"MANAGEMENT", - "datamanagement":"DATA MANAGEMENT", - "qchat":"Q-CHAT", - "groupmanagement":"GROUP MANAGEMENT", - "puzzles":"PUZZLES", - "nodemanagement":"NODE MANAGEMENT" + "sidemenu": { + "minting": "MINTING", + "mintingdetails": "MINTING DETAILS", + "becomeAMinter": "BECOME A MINTER", + "wallets": "WALLETS", + "tradeportal": "TRADE PORTAL", + "rewardshare": "REWARD SHARE", + "nameregistration": "NAME REGISTRATION", + "websites": "WEBSITES", + "management": "MANAGEMENT", + "datamanagement": "DATA MANAGEMENT", + "qchat": "Q-CHAT", + "groupmanagement": "GROUP MANAGEMENT", + "puzzles": "PUZZLES", + "nodemanagement": "NODE MANAGEMENT" }, - "login":{ - "login":"Login", - "createaccount":"Create Account", - "name":"Name", - "address":"Address", - "password":"Password", - "youraccounts":"Your accounts", - "clickto":"Click your account to login with it", - "needcreate":"You need to create or save an account before you can log in!", - "upload":"Upload your qortal backup", - "howlogin":"How would you like to login?", - "seed":"Seedphrase", - "seedphrase":"seedphrase", - "saved":"Saved account", - "qora":"Qora address seed", - "backup":"Qortal wallet backup", - "decrypt":"Decrypt backup", - "save":"Save in this browser.", - "prepare":"Preparing Your Account", - "areyousure":"Are you sure you want to remove this wallet from saved wallets?", - "error1":"Backup must be valid JSON", - "error2":"Login option not selected", - "createwelcome":"Welcome to Qortal, you will find it to be similar to that of an RPG game, you, as a minter on the Qortal network (if you choose to become one) will have the chance to level your account up, giving you both more of the QORT block reward and also larger influence over the network in terms of voting on decisions for the platform.", - "createa":"A", - "click":"Click to view seedphrase", - "confirmpass":"Confirm Password", - "willbe":"will be randomly generated in background. This is used as your private key generator for your blockchain account in Qortal.", - "clicknext":"Create your Qortal account by clicking NEXT below.", - "ready":"Your account is now ready to be created. It will be saved in this browser. If you do not want your new account to be saved in your browser, you can uncheck the box below. You will still be able to login with your new account(after logging out), using your wallet backup file that you MUST download once you create your account.", - "welmessage":"Welcome to Qortal", - "pleaseenter":"Please enter a Password!", - "notmatch":"Passwords not match!", - "lessthen8":"Your password is less than 8 characters! This is not recommended. You can continue to ignore this warning.", - "lessthen8-2":"Your password is less than 8 characters!", - "entername":"Please enter a Name!", - "downloaded":"Your Wallet BackUp file get downloaded!", - "loading":"Loading, Please wait...", - "createdseed":"Your created Seedphrase", - "saveseed":"Save Seedphrase", - "savein":"Save in browser", - "backup2":"This file is the ONLY way to access your account on a system that doesn't have it saved to the app/browser. BE SURE TO BACKUP THIS FILE IN MULTIPLE PLACES. The file is encrypted very securely and decrypted with your local password you created in the previous step. You can save it anywhere securely, but be sure to do that in multiple locations.", - "savewallet":"Save Wallet BackUp File", - "created1":"Your account is now created", - "created2":" and will be saved in this browser.", - "downloadbackup":"Download Wallet BackUp File", - "passwordhint":"A password must be at least 8 characters." + "login": { + "login": "Login", + "createaccount": "Create Account", + "name": "Name", + "address": "Address", + "password": "Password", + "youraccounts": "Your accounts", + "clickto": "Click your account to login with it", + "needcreate": "You need to create or save an account before you can log in!", + "upload": "Upload your qortal backup", + "howlogin": "How would you like to login?", + "seed": "Seedphrase", + "seedphrase": "seedphrase", + "saved": "Saved account", + "qora": "Qora address seed", + "backup": "Qortal wallet backup", + "decrypt": "Decrypt backup", + "save": "Save in this browser.", + "prepare": "Preparing Your Account", + "areyousure": "Are you sure you want to remove this wallet from saved wallets?", + "error1": "Backup must be valid JSON", + "error2": "Login option not selected", + "createwelcome": "Welcome to Qortal, you will find it to be similar to that of an RPG game, you, as a minter on the Qortal network (if you choose to become one) will have the chance to level your account up, giving you both more of the QORT block reward and also larger influence over the network in terms of voting on decisions for the platform.", + "createa": "A", + "click": "Click to view seedphrase", + "confirmpass": "Confirm Password", + "willbe": "will be randomly generated in background. This is used as your private key generator for your blockchain account in Qortal.", + "clicknext": "Create your Qortal account by clicking NEXT below.", + "ready": "Your account is now ready to be created. It will be saved in this browser. If you do not want your new account to be saved in your browser, you can uncheck the box below. You will still be able to login with your new account(after logging out), using your wallet backup file that you MUST download once you create your account.", + "welmessage": "Welcome to Qortal", + "pleaseenter": "Please enter a Password!", + "notmatch": "Passwords not match!", + "lessthen8": "Your password is less than 8 characters! This is not recommended. You can continue to ignore this warning.", + "lessthen8-2": "Your password is less than 8 characters!", + "entername": "Please enter a Name!", + "downloaded": "Your Wallet BackUp file get downloaded!", + "loading": "Loading, Please wait...", + "createdseed": "Your created Seedphrase", + "saveseed": "Save Seedphrase", + "savein": "Save in browser", + "backup2": "This file is the ONLY way to access your account on a system that doesn't have it saved to the app/browser. BE SURE TO BACKUP THIS FILE IN MULTIPLE PLACES. The file is encrypted very securely and decrypted with your local password you created in the previous step. You can save it anywhere securely, but be sure to do that in multiple locations.", + "savewallet": "Save Wallet BackUp File", + "created1": "Your account is now created", + "created2": " and will be saved in this browser.", + "downloadbackup": "Download Wallet BackUp File", + "passwordhint": "A password must be at least 8 characters." }, - "logout":{ - "logout":"LOGOUT", - "confirmlogout":"Are you sure you want to logout?" + "logout": { + "logout": "LOGOUT", + "confirmlogout": "Are you sure you want to logout?" }, - "fragfile":{ - "selectfile":"Select file", - "dragfile":"Drag and drop backup here" + "fragfile": { + "selectfile": "Select file", + "dragfile": "Drag and drop backup here" }, - "settings":{ - "generalinfo":"General Account Info", - "address":"Address", - "publickey":"Public Key", - "settings":"Settings", - "account":"Account", - "security":"Security", - "qr_login_menu_item":"QR Login", - "qr_login_description_1":"Scan this code to unlock your wallet on other device using the same password which you logged in with.", - "qr_login_description_2":"Choose a password which you will use to unlock your wallet on other device after scanning the QR code.", - "qr_login_button_1":"Show login QR code", - "qr_login_button_2":"Generate login QR code", - "notifications":"Notifications", - "accountsecurity":"Account Security", - "password":"Password", - "download":"Download Backup File", - "choose":"Please choose a password to encrypt your backup with. (This can be the same as the one you logged in with, or different)", - "block":"Block Notifications (Coming Soon...)", - "playsound":"Play Sound", - "shownotifications":"Show Notifications", - "nodeurl":"Node Url", - "nodehint":"Select a node from the default list of nodes above or add a custom node to the list above by clicking on the button below", - "addcustomnode":"Add Custom Node", - "addandsave":"Add And Save", - "protocol":"Protocol", - "domain":"Domain", - "port":"Port", - "import":"Import Nodes", - "export":"Export Nodes", - "deletecustomnode":"Remove All Custom Nodes", - "warning":"Your existing nodes will be deleted and from backup new created." + "settings": { + "generalinfo": "General Account Info", + "address": "Address", + "publickey": "Public Key", + "settings": "Settings", + "account": "Account", + "security": "Security", + "qr_login_menu_item": "QR Login", + "qr_login_description_1": "Scan this code to unlock your wallet on other device using the same password which you logged in with.", + "qr_login_description_2": "Choose a password which you will use to unlock your wallet on other device after scanning the QR code.", + "qr_login_button_1": "Show login QR code", + "qr_login_button_2": "Generate login QR code", + "notifications": "Notifications", + "accountsecurity": "Account Security", + "password": "Password", + "download": "Download Backup File", + "choose": "Please choose a password to encrypt your backup with. (This can be the same as the one you logged in with, or different)", + "block": "Block Notifications (Coming Soon...)", + "playsound": "Play Sound", + "shownotifications": "Show Notifications", + "nodeurl": "Node Url", + "nodehint": "Select a node from the default list of nodes above or add a custom node to the list above by clicking on the button below", + "addcustomnode": "Add Custom Node", + "addandsave": "Add And Save", + "protocol": "Protocol", + "domain": "Domain", + "port": "Port", + "import": "Import Nodes", + "export": "Export Nodes", + "deletecustomnode": "Remove All Custom Nodes", + "warning": "Your existing nodes will be deleted and from backup new created." }, - "appinfo":{ - "blockheight":"Block Height", - "uiversion":"UI Version", - "coreversion":"Core Version", - "minting":"(Minting)", - "synchronizing":"Synchronizing" + "appinfo": { + "blockheight": "Block Height", + "uiversion": "UI Version", + "coreversion": "Core Version", + "minting": "(Minting)", + "synchronizing": "Synchronizing" }, - "walletprofile":{ - "minterlevel":"Minter Level", - "blocksminted":"Blocks Minted" + "walletprofile": { + "minterlevel": "Minter Level", + "blocksminted": "Blocks Minted" }, - "general":{ - "yes":"Yes", - "no":"No", - "confirm":"Confirm", - "decline":"Decline", - "open":"Open", - "close":"Close", - "back":"Back", - "next":"Next", - "create":"Create", - "continue":"Continue", - "save":"Save" + "general": { + "yes": "Yes", + "no": "No", + "confirm": "Confirm", + "decline": "Decline", + "open": "Open", + "close": "Close", + "back": "Back", + "next": "Next", + "create": "Create", + "continue": "Continue", + "save": "Save" }, - "startminting":{ - "smchange1":"Cannot fetch minting accounts", - "smchange2":"Failed to remove key", - "smchange3":"Failed to add minting key", - "smchange4":"Cannot create sponsorship key", - "smchange5":"Creating relationship", - "smchange6":"Awaiting confirmation on blockchain", - "smchange7":"Finishing up relationship", - "smchange8":"Adding minting key to node", - "smchange9":"Complete", - "smchange10":"Only 2 minting keys are allowed per node, you are attempting to assign 3 keys, please go to management - node management, and remove the key you do not want to assign to this node, thank you!" + "startminting": { + "smchange1": "Cannot fetch minting accounts", + "smchange2": "Failed to remove key", + "smchange3": "Failed to add minting key", + "smchange4": "Cannot create sponsorship key", + "smchange5": "Creating relationship", + "smchange6": "Awaiting confirmation on blockchain", + "smchange7": "Finishing up relationship", + "smchange8": "Adding minting key to node", + "smchange9": "Complete", + "smchange10": "Only 2 minting keys are allowed per node, you are attempting to assign 3 keys, please go to management - node management, and remove the key you do not want to assign to this node, thank you!" }, - "mintingpage":{ - "mchange1":"General Minting Details", - "mchange2":"Blockchain Statistics", - "mchange3":"Avg. Qortal Blocktime", - "mchange4":"Avg. Blocks Per Day", - "mchange5":"Avg. Created QORT Per Day", - "mchange6":"Minting Account Details", - "mchange7":"Not A Minter", - "mchange8":"Minting", - "mchange9":"Not Minting", - "mchange10":"Activate Account Details", - "mchange11":"Not Activated", - "mchange12":"Activate Your Account", - "mchange13":"Introduction", - "mchange14":"To activate your account, an OUTGOING transaction needs to take place. Name Registration is the most common method. You can ask someone in Q-Chat to send you a small amount of QORT so that you may activate your account, or buy QORT within the Trade Portal then make an OUTGOING transaction of any kind and secure your public key on the blockchain. Until you do this, your public key is only known by you, in your UI, and no one else can pull your public key from the chain.", - "mchange15":"Current Status", - "mchange16":"Current Level", - "mchange17":"Blocks To Next Level", - "mchange18":"If you continue minting 24/7 you will reach level", - "mchange19":"Minting Rewards Info", - "mchange20":"Current Tier", - "mchange21":"Total Minters in The Tier", - "mchange22":"Tier Share Per Block", - "mchange23":"Est. Reward Per Block", - "mchange24":"Est. Reward Per Day", - "mchange25":"Seconds", - "mchange26":"Blocks", - "mchange27":"Level", - "mchange28":"Tier", - "mchange29":"days", - "mchange30":"Minters", - "mchange31":"Press for help", - "mchange32":"Become A Minter", - "mchange33":"Introduction", - "mchange34":"In Qortal, in order to become a minter and begin earning QORT rewards with your increase in Minter Level, you must first become ‘sponsored’. A sponsor in Qortal is any other minter of level 5 or higher, or a Qortal Founder. You will obtain a sponsorship key from the sponsor, and use that key to get to level 1. Once you have reached level 1, you will be able to create your own minting key and start earning rewards for helping secure the Qortal Blockchain.", - "mchange35":"Sponsorship", - "mchange36":"Your sponsor will issue you a ‘Sponsorship Key’ which you will use to add to your node, and begin minting (for no rewards until reaching level 1.) Once you reach level 1, you create/assign your own ‘Minting Key’ and begin earning rewards.", - "mchange37":"Simply reach out to a minter in Qortal who is high enough level to issue a sponsorship key, obtain that key, then come back here and input the key to begin your minting journey !", - "mchange38":"in" + "mintingpage": { + "mchange1": "General Minting Details", + "mchange2": "Blockchain Statistics", + "mchange3": "Avg. Qortal Blocktime", + "mchange4": "Avg. Blocks Per Day", + "mchange5": "Avg. Created QORT Per Day", + "mchange6": "Minting Account Details", + "mchange7": "Not A Minter", + "mchange8": "Minting", + "mchange9": "Not Minting", + "mchange10": "Activate Account Details", + "mchange11": "Not Activated", + "mchange12": "Activate Your Account", + "mchange13": "Introduction", + "mchange14": "To activate your account, an OUTGOING transaction needs to take place. Name Registration is the most common method. You can ask someone in Q-Chat to send you a small amount of QORT so that you may activate your account, or buy QORT within the Trade Portal then make an OUTGOING transaction of any kind and secure your public key on the blockchain. Until you do this, your public key is only known by you, in your UI, and no one else can pull your public key from the chain.", + "mchange15": "Current Status", + "mchange16": "Current Level", + "mchange17": "Blocks To Next Level", + "mchange18": "If you continue minting 24/7 you will reach level", + "mchange19": "Minting Rewards Info", + "mchange20": "Current Tier", + "mchange21": "Total Minters in The Tier", + "mchange22": "Tier Share Per Block", + "mchange23": "Est. Reward Per Block", + "mchange24": "Est. Reward Per Day", + "mchange25": "Seconds", + "mchange26": "Blocks", + "mchange27": "Level", + "mchange28": "Tier", + "mchange29": "days", + "mchange30": "Minters", + "mchange31": "Press for help", + "mchange32": "Become A Minter", + "mchange33": "Introduction", + "mchange34": "In Qortal, in order to become a minter and begin earning QORT rewards with your increase in Minter Level, you must first become ‘sponsored’. A sponsor in Qortal is any other minter of level 5 or higher, or a Qortal Founder. You will obtain a sponsorship key from the sponsor, and use that key to get to level 1. Once you have reached level 1, you will be able to create your own minting key and start earning rewards for helping secure the Qortal Blockchain.", + "mchange35": "Sponsorship", + "mchange36": "Your sponsor will issue you a ‘Sponsorship Key’ which you will use to add to your node, and begin minting (for no rewards until reaching level 1.) Once you reach level 1, you create/assign your own ‘Minting Key’ and begin earning rewards.", + "mchange37": "Simply reach out to a minter in Qortal who is high enough level to issue a sponsorship key, obtain that key, then come back here and input the key to begin your minting journey !", + "mchange38": "in" }, - "becomeMinterPage":{ - "bchange7":"Enter Sponsorship Key", - "bchange8":"Input key from your sponsor here", - "bchange10":"Current Sponsorship Status", - "bchange12":"Minting with sponsor key", - "bchange13":"Blocks Remaining in Sponsorship Period", - "bchange15":"Sponsorship Relationship", - "bchange16":"Sponsor Account", - "bchange17":"Copy Sponsorship Key", - "bchange18":"Start Minting", - "bchange19":"Success! You are currently minting." + "becomeMinterPage": { + "bchange7": "Enter Sponsorship Key", + "bchange8": "Input key from your sponsor here", + "bchange10": "Current Sponsorship Status", + "bchange12": "Minting with sponsor key", + "bchange13": "Blocks Remaining in Sponsorship Period", + "bchange15": "Sponsorship Relationship", + "bchange16": "Sponsor Account", + "bchange17": "Copy Sponsorship Key", + "bchange18": "Start Minting", + "bchange19": "Success! You are currently minting." }, - "walletpage":{ - "wchange1":"Fetching balance ...", - "wchange2":"Current Wallet", - "wchange3":"Copy wallet address to clipboard", - "wchange4":"Address copied to clipboard", - "wchange5":"Transaction Details", - "wchange6":"Transaction Type", - "wchange7":"OUT", - "wchange8":"IN", - "wchange9":"Sender", - "wchange10":"Receiver", - "wchange11":"Amount", - "wchange12":"Transaction Fee", - "wchange13":"Block", - "wchange14":"Time", - "wchange15":"Transaction Signature", - "wchange16":"Transaction Hash", - "wchange17":"Send", - "wchange18":"From address", - "wchange19":"Available balance", - "wchange20":"To (address or name)", - "wchange21":"Current static fee:", - "wchange22":"Wallets", - "wchange23":"To (address)", - "wchange24":"Current fee per byte", - "wchange25":"Low fees may result in slow or unconfirmed transactions.", - "wchange26":"Insufficient Funds!", - "wchange27":"Invalid Amount!", - "wchange28":"Receiver cannot be empty!", - "wchange29":"Invalid Receiver!", - "wchange30":"Transaction Successful!", - "wchange31":"Transaction Failed!", - "wchange32":"Failed to Fetch QORT Balance. Try again!", - "wchange33":"Failed to Fetch", - "wchange34":"Balance. Try again!", - "wchange35":"Type", - "wchange36":"Fee", - "wchange37":"Total Amount", - "wchange38":"Address has no transactions yet.", - "wchange39":"Unable to copy address.", - "wchange40":"PAYMENT", - "wchange41":"Status", - "wchange42":"Confirmations", - "wchange43":"Your transaction will not show until confirmed, be patient...", - "wchange44":"Please try again...", - "wchange45":"Send all", - "wchange46":"Send to this address", - "wchange47":"Address Book", - "wchange48":"This Address Book is empty !", - "wchange49":"Add to Address Book", - "wchange50":"Name cannot be empty!", - "wchange51":"Address cannot be empty!", - "wchange52":"Successfully added!", - "wchange53":"Import Address Book", - "wchange54":"Export Address Book", - "wchange55":"Your existing address book will be deleted and from backup new created.", - "wchange56":"WARNING!", - "wchange57":"Memo" + "walletpage": { + "wchange1": "Fetching balance ...", + "wchange2": "Current Wallet", + "wchange3": "Copy wallet address to clipboard", + "wchange4": "Address copied to clipboard", + "wchange5": "Transaction Details", + "wchange6": "Transaction Type", + "wchange7": "OUT", + "wchange8": "IN", + "wchange9": "Sender", + "wchange10": "Receiver", + "wchange11": "Amount", + "wchange12": "Transaction Fee", + "wchange13": "Block", + "wchange14": "Time", + "wchange15": "Transaction Signature", + "wchange16": "Transaction Hash", + "wchange17": "Send", + "wchange18": "From address", + "wchange19": "Available balance", + "wchange20": "To (address or name)", + "wchange21": "Current static fee:", + "wchange22": "Wallets", + "wchange23": "To (address)", + "wchange24": "Current fee per byte", + "wchange25": "Low fees may result in slow or unconfirmed transactions.", + "wchange26": "Insufficient Funds!", + "wchange27": "Invalid Amount!", + "wchange28": "Receiver cannot be empty!", + "wchange29": "Invalid Receiver!", + "wchange30": "Transaction Successful!", + "wchange31": "Transaction Failed!", + "wchange32": "Failed to Fetch QORT Balance. Try again!", + "wchange33": "Failed to Fetch", + "wchange34": "Balance. Try again!", + "wchange35": "Type", + "wchange36": "Fee", + "wchange37": "Total Amount", + "wchange38": "Address has no transactions yet.", + "wchange39": "Unable to copy address.", + "wchange40": "PAYMENT", + "wchange41": "Status", + "wchange42": "Confirmations", + "wchange43": "Your transaction will not show until confirmed, be patient...", + "wchange44": "Please try again...", + "wchange45": "Send all", + "wchange46": "Send to this address", + "wchange47": "Address Book", + "wchange48": "This Address Book is empty !", + "wchange49": "Add to Address Book", + "wchange50": "Name cannot be empty!", + "wchange51": "Address cannot be empty!", + "wchange52": "Successfully added!", + "wchange53": "Import Address Book", + "wchange54": "Export Address Book", + "wchange55": "Your existing address book will be deleted and from backup new created.", + "wchange56": "WARNING!", + "wchange57": "Memo" }, - "tradepage":{ - "tchange1":"Trade Portal", - "tchange2":"Select Trading Pair", - "tchange3":"HISTORIC MARKET TRADES", - "tchange4":"MY TRADE HISTORY", - "tchange5":"OPEN MARKET SELL ORDERS", - "tchange6":"MY ORDERS", - "tchange7":"Stuck Offers", - "tchange8":"Amount", - "tchange9":"Price", - "tchange10":"Total", - "tchange11":"Date", - "tchange12":"Status", - "tchange13":"Seller", - "tchange14":"Price Each", - "tchange15":"Clear Form", - "tchange16":"You have", - "tchange17":"Action", - "tchange18":"BUY", - "tchange19":"SELL", - "tchange20":"Failed to Create Trade. Try again!", - "tchange21":"Failed to Create Trade. Error Code", - "tchange22":"Insufficient Funds!", - "tchange23":"Buy Request Successful!", - "tchange24":"Buy Request Existing!", - "tchange25":"Failed to Create Trade. Error Code", - "tchange26":"Trade Cancelling In Progress!", - "tchange27":"Failed to Cancel Trade. Try again!", - "tchange28":"Failed to Cancel Trade. Error Code", - "tchange29":"CANCEL", - "tchange30":"Failed to Fetch Balance. Try again!", - "tchange31":"SOLD", - "tchange32":"BOUGHT", - "tchange33":"Exchange Rate" + "tradepage": { + "tchange1": "Trade Portal", + "tchange2": "Select Trading Pair", + "tchange3": "HISTORIC MARKET TRADES", + "tchange4": "MY TRADE HISTORY", + "tchange5": "OPEN MARKET SELL ORDERS", + "tchange6": "MY ORDERS", + "tchange7": "Stuck Offers", + "tchange8": "Amount", + "tchange9": "Price", + "tchange10": "Total", + "tchange11": "Date", + "tchange12": "Status", + "tchange13": "Seller", + "tchange14": "Price Each", + "tchange15": "Clear Form", + "tchange16": "You have", + "tchange17": "Action", + "tchange18": "BUY", + "tchange19": "SELL", + "tchange20": "Failed to Create Trade. Try again!", + "tchange21": "Failed to Create Trade. Error Code", + "tchange22": "Insufficient Funds!", + "tchange23": "Buy Request Successful!", + "tchange24": "Buy Request Existing!", + "tchange25": "Failed to Create Trade. Error Code", + "tchange26": "Trade Cancelling In Progress!", + "tchange27": "Failed to Cancel Trade. Try again!", + "tchange28": "Failed to Cancel Trade. Error Code", + "tchange29": "CANCEL", + "tchange30": "Failed to Fetch Balance. Try again!", + "tchange31": "SOLD", + "tchange32": "BOUGHT", + "tchange33": "Exchange Rate" }, - "rewardsharepage":{ - "rchange1":"Rewardshares", - "rchange2":"Create reward share", - "rchange3":"Rewardshares Involving In This Account", - "rchange4":"Minting Account", - "rchange5":"Share Percent", - "rchange6":"Recipient", - "rchange7":"Action", - "rchange8":"Type", - "rchange9":"Level 1 - 4 can create a Self Share and Level 5 or above can create a Reward Share!", - "rchange10":"Recipient Public Key", - "rchange11":"Reward share percentage", - "rchange12":"Doing something delicious", - "rchange13":"Adding minting account", - "rchange14":"Add", - "rchange15":"Account is not involved in any reward shares", - "rchange16":"Own Rewardshare", - "rchange17":"Remove", - "rchange18":"Cannot Create Multiple Reward Shares!", - "rchange19":"Cannot Create Multiple Self Shares!", - "rchange20":"CANNOT CREATE REWARD SHARE! at level", - "rchange21":"Reward Share Successful!", - "rchange22":"Reward Share Removed Successfully!" + "rewardsharepage": { + "rchange1": "Rewardshares", + "rchange2": "Create reward share", + "rchange3": "Rewardshares Involving In This Account", + "rchange4": "Minting Account", + "rchange5": "Share Percent", + "rchange6": "Recipient", + "rchange7": "Action", + "rchange8": "Type", + "rchange9": "Level 1 - 4 can create a Self Share and Level 5 or above can create a Reward Share!", + "rchange10": "Recipient Public Key", + "rchange11": "Reward share percentage", + "rchange12": "Doing something delicious", + "rchange13": "Adding minting account", + "rchange14": "Add", + "rchange15": "Account is not involved in any reward shares", + "rchange16": "Own Rewardshare", + "rchange17": "Remove", + "rchange18": "Cannot Create Multiple Reward Shares!", + "rchange19": "Cannot Create Multiple Self Shares!", + "rchange20": "CANNOT CREATE REWARD SHARE! at level", + "rchange21": "Reward Share Successful!", + "rchange22": "Reward Share Removed Successfully!" }, - "registernamepage":{ - "nchange1":"Name Registration", - "nchange2":"Register Name", - "nchange3":"Registered Names", - "nchange4":"Avatar", - "nchange5":"Name", - "nchange6":"Owner", - "nchange7":"Action", - "nchange8":"No names registered by this account!", - "nchange9":"Register a Name!", - "nchange10":"Description (optional)", - "nchange11":"Doing something delicious", - "nchange12":"Registering Name", - "nchange13":"The current name registration fee is", - "nchange14":"Register", - "nchange15":"Set Avatar", - "nchange16":"Need Core Update", - "nchange17":"Name Already Exists!", - "nchange18":"Name Registration Successful!" + "registernamepage": { + "nchange1": "Name Registration", + "nchange2": "Register Name", + "nchange3": "Registered Names", + "nchange4": "Avatar", + "nchange5": "Name", + "nchange6": "Owner", + "nchange7": "Action", + "nchange8": "No names registered by this account!", + "nchange9": "Register a Name!", + "nchange10": "Description (optional)", + "nchange11": "Doing something delicious", + "nchange12": "Registering Name", + "nchange13": "The current name registration fee is", + "nchange14": "Register", + "nchange15": "Set Avatar", + "nchange16": "Need Core Update", + "nchange17": "Name Already Exists!", + "nchange18": "Name Registration Successful!" }, - "websitespage":{ - "schange1":"Browse Websites", - "schange2":"Followed Websites", - "schange3":"Blocked Websites", - "schange4":"Search Websites", - "schange5":"Avatar", - "schange6":"Details", - "schange7":"Published by", - "schange8":"Actions", - "schange9":"Websites", - "schange10":"No websites available", - "schange11":"Your Followed Websites", - "schange12":"Followed Websites", - "schange13":"You aren't following any websites", - "schange14":"Your Blocked Websites", - "schange15":"Blocked Websites", - "schange16":"You have not blocked any websites", - "schange17":"Name Not Found!", - "schange18":"Relay mode is enabled. This means that your node will help to transport encrypted data around the network when a peer requests it. You can opt out by setting", - "schange19":"in", - "schange20":"Relay mode is disabled. You can enable it by setting", - "schange21":"Publish Website", - "schange22":"Error occurred when trying to follow this registered name. Please try again!", - "schange23":"Error occurred when trying to unfollow this registered name. Please try again!", - "schange24":"Error occurred when trying to block this registered name. Please try again!", - "schange25":"Error occurred when trying to unblock this registered name. Please try again!", - "schange26":"Uncategorized", - "schange27":"Size", - "schange28":"Status", - "schange29":"Follow", - "schange30":"Unfollow", - "schange31":"Block", - "schange32":"Unblock", - "schange33":"Name to search", - "schange34":"Name can not be empty!", - "schange35":"Search" + "websitespage": { + "schange1": "Browse Websites", + "schange2": "Followed Websites", + "schange3": "Blocked Websites", + "schange4": "Search Websites", + "schange5": "Avatar", + "schange6": "Details", + "schange7": "Published by", + "schange8": "Actions", + "schange9": "Websites", + "schange10": "No websites available", + "schange11": "Your Followed Websites", + "schange12": "Followed Websites", + "schange13": "You aren't following any websites", + "schange14": "Your Blocked Websites", + "schange15": "Blocked Websites", + "schange16": "You have not blocked any websites", + "schange17": "Name Not Found!", + "schange18": "Relay mode is enabled. This means that your node will help to transport encrypted data around the network when a peer requests it. You can opt out by setting", + "schange19": "in", + "schange20": "Relay mode is disabled. You can enable it by setting", + "schange21": "Publish Website", + "schange22": "Error occurred when trying to follow this registered name. Please try again!", + "schange23": "Error occurred when trying to unfollow this registered name. Please try again!", + "schange24": "Error occurred when trying to block this registered name. Please try again!", + "schange25": "Error occurred when trying to unblock this registered name. Please try again!", + "schange26": "Uncategorized", + "schange27": "Size", + "schange28": "Status", + "schange29": "Follow", + "schange30": "Unfollow", + "schange31": "Block", + "schange32": "Unblock", + "schange33": "Name to search", + "schange34": "Name can not be empty!", + "schange35": "Search" }, - "publishpage":{ - "pchange1":"Publish", - "pchange2":"Update", - "pchange3":"Note: it is recommended that you set up port forwarding before hosting data, so that it can more easily accessed by peers on the network.", - "pchange4":"Select Name", - "pchange5":"Title", - "pchange6":"Description", - "pchange7":"Select Category", - "pchange8":"Tag", - "pchange9":"Service", - "pchange10":"Identifier", - "pchange11":"Publish", - "pchange12":"Select zip file containing static content", - "pchange13":"Local path to static files", - "pchange14":"Please select a registered name to publish data for", - "pchange15":"Please select a file to host", - "pchange16":"Please select a zip file to host", - "pchange17":"Please enter the directory path containing the static content", - "pchange18":"Please enter a service name", - "pchange19":"Processing data... this can take some time...", - "pchange20":"Error:", - "pchange21":"Internal Server Error when publishing data", - "pchange22":"Computing proof of work... this can take some time...", - "pchange23":"Transaction successful!", - "pchange24":"Unable to sign and process transaction", - "pchange25":"Choose File" + "publishpage": { + "pchange1": "Publish", + "pchange2": "Update", + "pchange3": "Note: it is recommended that you set up port forwarding before hosting data, so that it can more easily accessed by peers on the network.", + "pchange4": "Select Name", + "pchange5": "Title", + "pchange6": "Description", + "pchange7": "Select Category", + "pchange8": "Tag", + "pchange9": "Service", + "pchange10": "Identifier", + "pchange11": "Publish", + "pchange12": "Select zip file containing static content", + "pchange13": "Local path to static files", + "pchange14": "Please select a registered name to publish data for", + "pchange15": "Please select a file to host", + "pchange16": "Please select a zip file to host", + "pchange17": "Please enter the directory path containing the static content", + "pchange18": "Please enter a service name", + "pchange19": "Processing data... this can take some time...", + "pchange20": "Error:", + "pchange21": "Internal Server Error when publishing data", + "pchange22": "Computing proof of work... this can take some time...", + "pchange23": "Transaction successful!", + "pchange24": "Unable to sign and process transaction", + "pchange25": "Choose File" }, - "browserpage":{ - "bchange1":"Forward", - "bchange2":"Reload", - "bchange3":"Back to list", - "bchange4":"Delete", - "bchange5":"from node", - "bchange6":"Your browser doesn't support iframes", - "bchange7":"Follow", - "bchange8":"Unfollow", - "bchange9":"Block", - "bchange10":"Unblock", - "bchange11":"Error occurred when trying to follow this registered name. Please try again!", - "bchange12":"Error occurred when trying to unfollow this registered name. Please try again!", - "bchange13":"Error occurred when trying to block this registered name. Please try again!", - "bchange14":"Error occurred when trying to unblock this registered name. Please try again!", - "bchange15":"Can't delete data from followed names. Please unfollow first.", - "bchange16":"Error occurred when trying to delete this resource. Please try again!" + "browserpage": { + "bchange1": "Forward", + "bchange2": "Reload", + "bchange3": "Back to list", + "bchange4": "Delete", + "bchange5": "from node", + "bchange6": "Your browser doesn't support iframes", + "bchange7": "Follow", + "bchange8": "Unfollow", + "bchange9": "Block", + "bchange10": "Unblock", + "bchange11": "Error occurred when trying to follow this registered name. Please try again!", + "bchange12": "Error occurred when trying to unfollow this registered name. Please try again!", + "bchange13": "Error occurred when trying to block this registered name. Please try again!", + "bchange14": "Error occurred when trying to unblock this registered name. Please try again!", + "bchange15": "Can't delete data from followed names. Please unfollow first.", + "bchange16": "Error occurred when trying to delete this resource. Please try again!" }, - "datapage":{ - "dchange1":"Data Management", - "dchange2":"Search in hosted data by this node", - "dchange3":"Data to search", - "dchange4":"Search", - "dchange5":"Registered Name", - "dchange6":"Service", - "dchange7":"Identifier", - "dchange8":"Actions", - "dchange9":"Data hosted by this node", - "dchange10":"Data name can not be empty!", - "dchange11":"Data not found!", - "dchange12":"Couldn't fetch hosted data list from node", - "dchange13":"This node isn't hosting any data", - "dchange14":"Unfollow", - "dchange15":"Delete", - "dchange16":"Block", - "dchange17":"Unblock", - "dchange18":"Error occurred when trying to block this registered name. Please try again!", - "dchange19":"Error occurred when trying to unfollow this registered name. Please try again!", - "dchange20":"Error occurred when trying to unblock this registered name. Please try again!", - "dchange21":"Error occurred when trying to delete this resource. Please try again!" + "datapage": { + "dchange1": "Data Management", + "dchange2": "Search in hosted data by this node", + "dchange3": "Data to search", + "dchange4": "Search", + "dchange5": "Registered Name", + "dchange6": "Service", + "dchange7": "Identifier", + "dchange8": "Actions", + "dchange9": "Data hosted by this node", + "dchange10": "Data name can not be empty!", + "dchange11": "Data not found!", + "dchange12": "Couldn't fetch hosted data list from node", + "dchange13": "This node isn't hosting any data", + "dchange14": "Unfollow", + "dchange15": "Delete", + "dchange16": "Block", + "dchange17": "Unblock", + "dchange18": "Error occurred when trying to block this registered name. Please try again!", + "dchange19": "Error occurred when trying to unfollow this registered name. Please try again!", + "dchange20": "Error occurred when trying to unblock this registered name. Please try again!", + "dchange21": "Error occurred when trying to delete this resource. Please try again!" }, - "chatpage":{ - "cchange1":"New Private Message", - "cchange2":"Loading...", - "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", - "cchange8":"Message...", - "cchange9":"Send", - "cchange10":"Blocked Users List", - "cchange11":"Name", - "cchange12":"Owner", - "cchange13":"Action", - "cchange14":"This account has not blocked any users.", - "cchange15":"No registered name", - "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...", - "cchange20":"Message Sent Successfully!", - "cchange21":"Sending failed, Please retry...", - "cchange22":"Loading Messages...", - "cchange23":"Cannot Decrypt Message!", - "cchange24":"Maximum Characters per message is 255" + "chatpage": { + "cchange1": "New Private Message", + "cchange2": "Loading...", + "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", + "cchange8": "Message...", + "cchange9": "Send", + "cchange10": "Blocked Users List", + "cchange11": "Name", + "cchange12": "Owner", + "cchange13": "Action", + "cchange14": "This account has not blocked any users.", + "cchange15": "No registered name", + "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...", + "cchange20": "Message Sent Successfully!", + "cchange21": "Sending failed, Please retry...", + "cchange22": "Loading Messages...", + "cchange23": "Cannot Decrypt Message!", + "cchange24": "Maximum Characters per message is 255" }, - "welcomepage":{ - "wcchange1":"Welcome to Q-Chat", - "wcchange2":"New Private Message", - "wcchange3":"Type the name or address of who you want to chat with to send a private message!", - "wcchange4":"Name / Address", - "wcchange5":"Message...", - "wcchange6":"Send", - "wcchange7":"Invalid Name / Address, Check the name / address and retry...", - "wcchange8":"Message Sent Successfully!", - "wcchange9":"Sending failed, Please retry..." + "welcomepage": { + "wcchange1": "Welcome to Q-Chat", + "wcchange2": "New Private Message", + "wcchange3": "Type the name or address of who you want to chat with to send a private message!", + "wcchange4": "Name / Address", + "wcchange5": "Message...", + "wcchange6": "Send", + "wcchange7": "Invalid Name / Address, Check the name / address and retry...", + "wcchange8": "Message Sent Successfully!", + "wcchange9": "Sending failed, Please retry..." }, - "blockpage":{ - "bcchange1":"Block User", - "bcchange2":"Successfully blocked this user!", - "bcchange3":"Error occurred when trying to block this user. Please try again!", - "bcchange4":"No registered name", - "bcchange5":"Block User Request", - "bcchange6":"Are you sure to block this user ?", - "bcchange7":"MENU", - "bcchange8":"Copy Address", - "bcchange9":"Private Message", - "bcchange10":"More" + "blockpage": { + "bcchange1": "Block User", + "bcchange2": "Successfully blocked this user!", + "bcchange3": "Error occurred when trying to block this user. Please try again!", + "bcchange4": "No registered name", + "bcchange5": "Block User Request", + "bcchange6": "Are you sure to block this user ?", + "bcchange7": "MENU", + "bcchange8": "Copy Address", + "bcchange9": "Private Message", + "bcchange10": "More", + "bcchange11": "Reply" }, - "grouppage":{ - "gchange1":"Qortal Groups", - "gchange2":"Create Group", - "gchange3":"Your Joined Groups", - "gchange4":"Group Name", - "gchange5":"Description", - "gchange6":"Role", - "gchange7":"Action", - "gchange8":"Not a member of any group!", - "gchange9":"Public Groups", - "gchange10":"Owner", - "gchange11":"No Open Public Groups available!", - "gchange12":"Create a New Group", - "gchange13":"Group Type", - "gchange14":"This Field is Required", - "gchange15":"Select an option", - "gchange16":"Public", - "gchange17":"Private", - "gchange18":"Group Approval Threshold (number / percentage of Admins that must approve a transaction):", - "gchange19":"NONE", - "gchange20":"ONE", - "gchange21":"Minimum Block delay for Group Transaction Approvals:", - "gchange22":"minutes", - "gchange23":"hour", - "gchange24":"hours", - "gchange25":"day", - "gchange26":"days", - "gchange27":"Maximum Block delay for Group Transaction Approvals:", - "gchange28":"Creating Group", - "gchange29":"Create Group", - "gchange30":"Join Group Request", - "gchange31":"Date Created", - "gchange32":"Date Updated", - "gchange33":"Joining", - "gchange34":"Join Group", - "gchange35":"Leave Group Request", - "gchange36":"Leaving", - "gchange37":"Leave Group", - "gchange38":"Manage Group Owner:", - "gchange39":"Manage Group Admin:", - "gchange40":"Manage Group", - "gchange41":"Group Creation Successful!", - "gchange42":"Invalid Group Name", - "gchange43":"Invalid Group Description", - "gchange44":"Select a Group Typ", - "gchange45":"Select a Group Approval Threshold", - "gchange46":"Select a Minimum Block delay for Group Transaction Approvals", - "gchange47":"Select a Maximum Block delay for Group Transaction Approvals", - "gchange48":"Join Group Request Sent Successfully!", - "gchange49":"Leave Group Request Sent Successfully!", - "gchange50":"Leave", - "gchange51":"Join", - "gchange52":"Admin", - "gchange53":"Member", - "gchange54":"Members" + "grouppage": { + "gchange1": "Qortal Groups", + "gchange2": "Create Group", + "gchange3": "Your Joined Groups", + "gchange4": "Group Name", + "gchange5": "Description", + "gchange6": "Role", + "gchange7": "Action", + "gchange8": "Not a member of any group!", + "gchange9": "Public Groups", + "gchange10": "Owner", + "gchange11": "No Open Public Groups available!", + "gchange12": "Create a New Group", + "gchange13": "Group Type", + "gchange14": "This Field is Required", + "gchange15": "Select an option", + "gchange16": "Public", + "gchange17": "Private", + "gchange18": "Group Approval Threshold (number / percentage of Admins that must approve a transaction):", + "gchange19": "NONE", + "gchange20": "ONE", + "gchange21": "Minimum Block delay for Group Transaction Approvals:", + "gchange22": "minutes", + "gchange23": "hour", + "gchange24": "hours", + "gchange25": "day", + "gchange26": "days", + "gchange27": "Maximum Block delay for Group Transaction Approvals:", + "gchange28": "Creating Group", + "gchange29": "Create Group", + "gchange30": "Join Group Request", + "gchange31": "Date Created", + "gchange32": "Date Updated", + "gchange33": "Joining", + "gchange34": "Join Group", + "gchange35": "Leave Group Request", + "gchange36": "Leaving", + "gchange37": "Leave Group", + "gchange38": "Manage Group Owner:", + "gchange39": "Manage Group Admin:", + "gchange40": "Manage Group", + "gchange41": "Group Creation Successful!", + "gchange42": "Invalid Group Name", + "gchange43": "Invalid Group Description", + "gchange44": "Select a Group Typ", + "gchange45": "Select a Group Approval Threshold", + "gchange46": "Select a Minimum Block delay for Group Transaction Approvals", + "gchange47": "Select a Maximum Block delay for Group Transaction Approvals", + "gchange48": "Join Group Request Sent Successfully!", + "gchange49": "Leave Group Request Sent Successfully!", + "gchange50": "Leave", + "gchange51": "Join", + "gchange52": "Admin", + "gchange53": "Member", + "gchange54": "Members" }, - "puzzlepage":{ - "pchange1":"Puzzles", - "pchange2":"Reward", - "pchange3":"SOLVED by", - "pchange4":"Name", - "pchange5":"Description", - "pchange6":"Clue / Answer", - "pchange7":"Action", - "pchange8":"Guess", - "pchange9":"Enter your guess to solve this puzzle and win", - "pchange10":"Your guess needs to be 43 or 44 characters and", - "pchange11":"not", - "pchange12":"include 0 (zero), I (upper i), O (upper o) or l (lower L).", - "pchange13":"Your Guess", - "pchange14":"Checking your guess...", - "pchange15":"Submit", - "pchange16":"Guess incorrect!", - "pchange17":"Reward claim submitted - check wallet for reward!" + "puzzlepage": { + "pchange1": "Puzzles", + "pchange2": "Reward", + "pchange3": "SOLVED by", + "pchange4": "Name", + "pchange5": "Description", + "pchange6": "Clue / Answer", + "pchange7": "Action", + "pchange8": "Guess", + "pchange9": "Enter your guess to solve this puzzle and win", + "pchange10": "Your guess needs to be 43 or 44 characters and", + "pchange11": "not", + "pchange12": "include 0 (zero), I (upper i), O (upper o) or l (lower L).", + "pchange13": "Your Guess", + "pchange14": "Checking your guess...", + "pchange15": "Submit", + "pchange16": "Guess incorrect!", + "pchange17": "Reward claim submitted - check wallet for reward!" }, - "nodepage":{ - "nchange1":"Node management for:", - "nchange2":"Node has been online for:", - "nchange3":"Node's minting accounts", - "nchange4":"Add minting account", - "nchange5":"If you would like to mint with your own account you will need to create a rewardshare transaction to yourself (with rewardshare percent set to 0), and then mint with the rewardshare key it gives you.", - "nchange6":"Rewardshare key", - "nchange7":"Adding minting account", - "nchange8":"Add", - "nchange9":"Minting Account", - "nchange10":"Recipient Account", - "nchange11":"Action", - "nchange12":"Remove", - "nchange13":"No minting accounts found for this node", - "nchange14":"Peers connected to this node", - "nchange15":"Add peer", - "nchange16":"Type the peer you wish to add's address below", - "nchange17":"Peer Address", - "nchange18":"Address", - "nchange19":"Last Height", - "nchange20":"Build Version", - "nchange21":"Connected for", - "nchange22":"Action", - "nchange23":"Force Sync", - "nchange24":"Node has no connected peers", - "nchange25":"Starting Sync with Peer: ", - "nchange26":"Successfully removed Peer: ", - "nchange27":"Minting Node Added Successfully!", - "nchange28":"Failed to Add Minting Node!", - "nchange29":"Successfully Removed Minting Account!", - "nchange30":"Failed to Remove Minting Account!", - "nchange31":"Stop Node", - "nchange32":"Successfully Sent Stop Request!" + "nodepage": { + "nchange1": "Node management for:", + "nchange2": "Node has been online for:", + "nchange3": "Node's minting accounts", + "nchange4": "Add minting account", + "nchange5": "If you would like to mint with your own account you will need to create a rewardshare transaction to yourself (with rewardshare percent set to 0), and then mint with the rewardshare key it gives you.", + "nchange6": "Rewardshare key", + "nchange7": "Adding minting account", + "nchange8": "Add", + "nchange9": "Minting Account", + "nchange10": "Recipient Account", + "nchange11": "Action", + "nchange12": "Remove", + "nchange13": "No minting accounts found for this node", + "nchange14": "Peers connected to this node", + "nchange15": "Add peer", + "nchange16": "Type the peer you wish to add's address below", + "nchange17": "Peer Address", + "nchange18": "Address", + "nchange19": "Last Height", + "nchange20": "Build Version", + "nchange21": "Connected for", + "nchange22": "Action", + "nchange23": "Force Sync", + "nchange24": "Node has no connected peers", + "nchange25": "Starting Sync with Peer: ", + "nchange26": "Successfully removed Peer: ", + "nchange27": "Minting Node Added Successfully!", + "nchange28": "Failed to Add Minting Node!", + "nchange29": "Successfully Removed Minting Account!", + "nchange30": "Failed to Remove Minting Account!", + "nchange31": "Stop Node", + "nchange32": "Successfully Sent Stop Request!" }, - "transpage":{ - "tchange1":"Transaction request", - "tchange2":"Decline", - "tchange3":"Confirm", - "tchange4":"To", - "tchange5":"Amount" + "transpage": { + "tchange1": "Transaction request", + "tchange2": "Decline", + "tchange3": "Confirm", + "tchange4": "To", + "tchange5": "Amount" }, - "apipage":{ - "achange1":"Add API key", - "achange2":"API key", - "achange3":"Please enter the API key for this node. It can be found in a file called “apikey.txt“ in the directory where the core is installed. Alternatively, click Cancel to use the core with reduced functionality.", - "achange4":"Cancel", - "achange5":"Add", - "achange6":"Successfully added API Key", - "achange7":"API key wrong, no API key added" + "apipage": { + "achange1": "Add API key", + "achange2": "API key", + "achange3": "Please enter the API key for this node. It can be found in a file called “apikey.txt“ in the directory where the core is installed. Alternatively, click Cancel to use the core with reduced functionality.", + "achange4": "Cancel", + "achange5": "Add", + "achange6": "Successfully added API Key", + "achange7": "API key wrong, no API key added" }, - "transactions":{ - "amount":"Amount", - "to":"To", - "declined":"User declined transaction!", - "namedialog1":"You are registering the name below:", - "namedialog2":"On pressing confirm, the name will be registered!", - "groupdialog1":"You are requesting to join the group below:", - "groupdialog2":"On pressing confirm, the group join request will be sent!", - "groupdialog3":"You are requesting to leave the group below:", - "groupdialog4":"On pressing confirm, the leave group request will be sent!", - "groupdialog5":"You are requesting to creating the group below:", - "groupdialog6":"On pressing confirm, the group creating request will be sent!", - "rewarddialog1":"Would you like to create a reward share transaction, sharing", - "rewarddialog2":"of your minting rewards with", - "rewarddialog3":"If yes, you will need to save the key below in order to mint. It can be supplied to any node in order to allow it to mint on your behalf.", - "rewarddialog4":"On pressing confirm, the rewardshare will be created, but you will still need to supply the above key to a node in order to mint with the account.", - "rewarddialog5":"You are removing a reward share transaction associated with account:", - "rewarddialog6":"On pressing confirm, the rewardshare will be removed and the minting key will become invalid." + "transactions": { + "amount": "Amount", + "to": "To", + "declined": "User declined transaction!", + "namedialog1": "You are registering the name below:", + "namedialog2": "On pressing confirm, the name will be registered!", + "groupdialog1": "You are requesting to join the group below:", + "groupdialog2": "On pressing confirm, the group join request will be sent!", + "groupdialog3": "You are requesting to leave the group below:", + "groupdialog4": "On pressing confirm, the leave group request will be sent!", + "groupdialog5": "You are requesting to creating the group below:", + "groupdialog6": "On pressing confirm, the group creating request will be sent!", + "rewarddialog1": "Would you like to create a reward share transaction, sharing", + "rewarddialog2": "of your minting rewards with", + "rewarddialog3": "If yes, you will need to save the key below in order to mint. It can be supplied to any node in order to allow it to mint on your behalf.", + "rewarddialog4": "On pressing confirm, the rewardshare will be created, but you will still need to supply the above key to a node in order to mint with the account.", + "rewarddialog5": "You are removing a reward share transaction associated with account:", + "rewarddialog6": "On pressing confirm, the rewardshare will be removed and the minting key will become invalid." }, - "sponsorshipspage":{ - "schange1":"Active Sponsorships", - "schange2":"Account Address", - "schange3":"Total Sponsorships active", - "schange4":"Next sponsorship ending in", - "schange5":"Sponsor New Minter", - "schange6":"Finished Sponsorships", - "schange7":"Completed", - "schange8":"Addresses", - "schange9":"You currently have no active sponsorships", - "schange10":"Public Key Lookup", - "schange11":"Copy", - "schange12":"Address to Public Key Converter", - "schange13":"Enter address", - "schange14":"In progress", - "schange15":"Finishing up", - "schange16":"Copy the key below and share it with your sponsored person.", - "schange17":"Copied to clipboard", - "schange18":"Warning: do not leave this plugin or close the Qortal UI until completion!", - "schange19":"Copy Sponsorship Key", - "schange20":"Creating relationship", - "schange21":"Remove Sponsorship Key" + "sponsorshipspage": { + "schange1": "Active Sponsorships", + "schange2": "Account Address", + "schange3": "Total Sponsorships active", + "schange4": "Next sponsorship ending in", + "schange5": "Sponsor New Minter", + "schange6": "Finished Sponsorships", + "schange7": "Completed", + "schange8": "Addresses", + "schange9": "You currently have no active sponsorships", + "schange10": "Public Key Lookup", + "schange11": "Copy", + "schange12": "Address to Public Key Converter", + "schange13": "Enter address", + "schange14": "In progress", + "schange15": "Finishing up", + "schange16": "Copy the key below and share it with your sponsored person.", + "schange17": "Copied to clipboard", + "schange18": "Warning: do not leave this plugin or close the Qortal UI until completion!", + "schange19": "Copy Sponsorship Key", + "schange20": "Creating relationship", + "schange21": "Remove Sponsorship Key" } -} +} \ No newline at end of file diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index 69d18120..d572a6cb 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -46,6 +46,7 @@ class ChatPage extends LitElement { hideNewMesssageBar: { attribute: false }, chatEditorPlaceholder: { type: String }, messagesRendered: { type: Array }, + repliedToSignature: { type: String }, } } @@ -135,9 +136,11 @@ class ChatPage extends LitElement { this.isPasteMenuOpen = false this.chatEditorPlaceholder = this.renderPlaceholder() this.messagesRendered = [] + this.repliedToSignature = '' } render() { + return html` ${this.isLoadingMessages ? html`

${translate("chatpage.cchange22")}

` : this.renderChatScroller(this._initialMessages)}
@@ -281,7 +284,17 @@ class ChatPage extends LitElement { } renderChatScroller(initialMessages) { - return html` ` + return html` + + ` } async getUpdateComplete() { @@ -385,6 +398,11 @@ class ChatPage extends LitElement { } } + setRepliedToSignature(messageSignature) { + console.log(messageSignature, "Replied To Message Signature Here") + this.repliedToSignature = messageSignature; + } + /** * New Message Template implementation, takes in a message object. * @param { Object } messageObj @@ -642,6 +660,13 @@ class ChatPage extends LitElement { } _sendMessage() { + // have params to determine if it's a reply or not + // have variable to determine if it's a response, holds signature in constructor + // need original message signature + // need whole original message object, transform the data and put it in local storage + // create new var called repliedToData and use that to modify the UI + // find specific object property in local + this.isLoading = true; this.chatEditor.disable(); const messageText = this.mirrorChatInput.value; @@ -658,8 +683,24 @@ class ChatPage extends LitElement { this.chatEditor.enable(); let err1string = get("chatpage.cchange24"); parentEpml.request('showSnackBar', `${err1string}`); + } else if(this.repliedToSignature) { + const messageObject = { + messageText, + images: [''], + repliedTo: this.repliedToSignature, + version: 1 + } + const stringifyMessageObject = JSON.stringify(messageObject) + this.sendMessage(stringifyMessageObject); } else { - this.sendMessage(trimmedMessage); + const messageObject = { + messageText, + images: [''], + repliedTo: '', + version: 1 + } + const stringifyMessageObject = JSON.stringify(messageObject) + this.sendMessage(stringifyMessageObject); } } diff --git a/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js b/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js index c18a6bcc..898144a8 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js +++ b/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js @@ -68,7 +68,7 @@ export const chatStyles = css` .message-data { width: 92%; margin-bottom: 15px; - margin-left: 50px; + margin-left: 55px; } .message-data-name { @@ -89,48 +89,76 @@ export const chatStyles = css` padding-bottom: 4px; } + .chat-bubble-container { + display:flex; + gap: 7px; + } + .message-container { position: relative; + display: flex; + flex-grow: 0; + flex-direction: column; + align-items: flex-start; + justify-content: center; + background-color: whitesmoke; + border-radius: 5px; + padding: 10px 15px; + gap: 10px; + margin-bottom: 20px; + } + + .original-message { + position: relative; + color: black; + line-height: 19px; + overflow-wrap: break-word; + user-select: text; + font-size: 16px; + width: 90%; + border-radius: 5px; + background-color: rgba(209, 209, 209, 0.79); + padding: 8px 5px 8px 25px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + + .original-message:before { + content: ""; + position: absolute; + top: 5px; + left: 10px; + height: 75%; + width: 2.6px; + background-color: rgb(14, 190, 240); } .message { color: black; - padding: 12px 10px; line-height: 19px; - white-space: pre-line; word-wrap: break-word; -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%; 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 8px 3px 3px; + width: 42px; + height: 42px; + float: left; } .message-parent:hover .chat-hover { display: block; } - .message-parent:hover .message{ + .message-parent:hover .message-container { filter:brightness(0.90); } @@ -138,7 +166,7 @@ export const chatStyles = css` display: none; position: absolute; top: -38px; - left: 88.2%; + right: 20px; } .emoji { @@ -154,21 +182,11 @@ export const chatStyles = css` 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; } @@ -269,7 +287,7 @@ export const chatStyles = css` .block-user-container { display: block; position: absolute; - left: -48px; + left: -5px; } .block-user { @@ -277,7 +295,7 @@ export const chatStyles = css` border: 1px solid rgb(218, 217, 217); border-radius: 5px; background-color: white; - width: 100%; + width: 90px; height: 32px; padding: 3px 8px; box-shadow: rgba(77, 77, 82, 0.2) 0px 7px 29px 0px; diff --git a/qortal-ui-plugins/plugins/core/components/ChatScroller.js b/qortal-ui-plugins/plugins/core/components/ChatScroller.js index 92c95673..f8a05c56 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatScroller.js +++ b/qortal-ui-plugins/plugins/core/components/ChatScroller.js @@ -24,7 +24,9 @@ class ChatScroller extends LitElement { 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 }, + setRepliedToSignature: {type: Function}, + repliedToSignature: {type: String}, } } @@ -47,7 +49,16 @@ class ChatScroller extends LitElement { ${repeat( this.messages, (message) => message.reference, - (message) => html`` + (message) => html` + + ` )}
@@ -137,7 +148,9 @@ class MessageTemplate extends LitElement { hideMessages: { type: Array }, openDialogPrivateMessage: {type: Boolean}, openDialogBlockUser: {type: Boolean}, - showBlockAddressIcon: { type: Boolean } + showBlockAddressIcon: { type: Boolean }, + setRepliedToSignature: {type: Function}, + repliedToSignature: {type: String}, } } @@ -180,8 +193,30 @@ class MessageTemplate extends LitElement { } render() { + console.log(this.messageObj, "here244") const hidemsg = this.hideMessages - + let message = "" + let repliedToData = null + try { + const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage) + message = parsedMessageObj.messageText + repliedToData = { + "timestamp": 1663419371885, + "txGroupId": 0, + "reference": "5LuncmE2RsGVdQizkZLnjgqU8QozR2hHhkiSujUgywEfqAvm6RW4xZ7c9XjuMnb76bNmX2ntRNhnBF4ErvawM1dW", + "senderPublicKey": "xmZXCYzGU2t3S6Ehm2zp4pVm83q9d143NKRgmiU1dXW", + "sender": "Qj9aLrdK2FLQY6YssRQUkDmXNJCko2zF7e", + "senderName": "GiseleH", + "data": "3JLP9vViLoRPJ1Pqt2uC6Ufqf7wgTrs4HuV4Ltgwdnf5ekcBCCf5MTm2Sg3sXHeuVnCpoJAyVdqgAbr7tcBoq3soNZTjteusXjjW3NSMNcJEAadaTYC68xGXGmvK1jRyioPqGaKiXKzR2jPPRV5SyiPd66788Z2Rqt3VQB98rvronX5w5tE9UUWRor6bmMeVL3dj7fHYhLPPE5VBpCS9Eskti7vnTgDUQxnjfr", + "isText": true, + "isEncrypted": false, + "signature": "3jWvhQKSDt4Zqeup5sLfyNksVVphFW5iF11PsTZzXQLCCPH9pDMqwNoKE2oe3DPYz47VbbLgJaAWMVA44z9dUr9U", + "decodedMessage": "for TrentB512 who computer crashed your registered name in qortal for your level 3 account was TrentB512 https://exqlorer.com/address/Qf58otnEXeoyvD7dvYmfEGpQ64oMD3uvwM" + + } + } catch (error) { + message = this.messageObj.decodedMessage + } let avatarImg = '' let nameMenu = '' let levelFounder = '' @@ -209,9 +244,20 @@ class MessageTemplate extends LitElement { ${levelFounder}
-
${avatarImg}
-
-
${unsafeHTML(this.emojiPicker.parse(this.escapeHTML(this.messageObj.decodedMessage)))}
+
+ ${avatarImg} +
+
+ ${repliedToData && html` +
+ ${repliedToData.decodedMessage} +
+ `} +
+ ${unsafeHTML(this.emojiPicker.parse(this.escapeHTML(message)))} +
this.showBlockUserModal()} .showBlockIconFunc=${(props) => this.showBlockIconFunc(props)} .showBlockAddressIcon=${this.showBlockAddressIcon} + .originalMessageSignature=${this.messageObj.signature} + .setRepliedToSignature=${this.setRepliedToSignature} + .repliedToSignature=${this.repliedToSignature} @blur=${() => this.showBlockIconFunc(false)} > @@ -250,7 +299,10 @@ class ChatMenu extends LitElement { showBlockUserModal: {type: Function}, toblockaddress: { type: String, attribute: true }, showBlockIconFunc: {type: Function}, - showBlockAddressIcon: {type: Boolean} + showBlockAddressIcon: {type: Boolean}, + originalMessageSignature: {type: String}, + setRepliedToSignature: {type: Function}, + repliedToSignature: {type: String}, } } @@ -278,13 +330,16 @@ class ChatMenu extends LitElement { render() { return html` -
+
+ From e5d470843a827e432e31e818aba9ca9a63ab57c6 Mon Sep 17 00:00:00 2001 From: Phillip Lang Martinez Date: Sat, 24 Sep 2022 19:39:01 +0300 Subject: [PATCH 003/123] save messages locally and load them if they exist --- qortal-ui-plugins/package.json | 5 +- .../plugins/core/components/ChatPage.js | 165 +++++++++++++++--- 2 files changed, 140 insertions(+), 30 deletions(-) diff --git a/qortal-ui-plugins/package.json b/qortal-ui-plugins/package.json index af41573a..6a05c76c 100644 --- a/qortal-ui-plugins/package.json +++ b/qortal-ui-plugins/package.json @@ -19,7 +19,8 @@ "dependencies": { "@material/mwc-list": "0.27.0", "@material/mwc-select": "0.27.0", - "emoji-picker-js": "https://github.com/Qortal/emoji-picker-js" + "emoji-picker-js": "https://github.com/Qortal/emoji-picker-js", + "localforage": "^1.10.0" }, "devDependencies": { "@babel/core": "7.19.1", @@ -62,4 +63,4 @@ "engines": { "node": ">=14.17.0" } -} \ No newline at end of file +} diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index f07cebdd..970029f3 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -2,7 +2,7 @@ import { LitElement, html, css } from 'lit' import { render } from 'lit/html.js' import { Epml } from '../../../epml.js' import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' - +import localForage from "localforage"; registerTranslateConfig({ loader: lang => fetch(`/language/${lang}.json`).then(res => res.json()) }) @@ -20,6 +20,10 @@ import '@material/mwc-button' import '@material/mwc-dialog' import '@material/mwc-icon' +const messagesCache = localForage.createInstance({ + name: "messages-cache", +}); + const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) class ChatPage extends LitElement { @@ -152,9 +156,12 @@ class ChatPage extends LitElement { ` } - firstUpdated() { + async firstUpdated() { + const keys = await messagesCache.keys() + console.log({ keys }) // TODO: Load and fetch messages from localstorage (maybe save messages to localstorage...) + // this.changeLanguage(); this.emojiPickerHandler = this.shadowRoot.querySelector('.emoji-button'); this.mirrorChatInput = this.shadowRoot.getElementById('messageBox'); @@ -263,6 +270,20 @@ class ChatPage extends LitElement { parentEpml.imReady(); } + async updated(changedProperties) { + if (changedProperties.has('messagesRendered')) { + let data = {} + console.log('chatId', this._chatId) + console.log(this.isReceipient) + const chatReference1 = this.isReceipient ? 'direct' : 'group'; + const chatReference2 = this._chatId + if (chatReference1 && chatReference2) { + await messagesCache.setItem(`${chatReference1}-${chatReference2}`, this.messagesRendered); + } + + } + } + changeLanguage() { const checkLanguage = localStorage.getItem('qortalLanguage') @@ -293,25 +314,60 @@ class ChatPage extends LitElement { async getOldMessage(scrollElement) { - if (this._messages.length <= 15 && this._messages.length >= 1) { // 15 is the default number of messages... - let __msg = [...this._messages] - this._messages = [] - this.messagesRendered = [...__msg, ...this.messagesRendered] + + + if (this.isReceipient) { + const getInitialMessages = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=20&reverse=true&before=${scrollElement.messageObj.timestamp}`, + }); + + const decodeMsgs = getInitialMessages.map((eachMessage) => { + + return this.decodeMessage(eachMessage) + + + + + + }) + + this.messagesRendered = [...decodeMsgs, ...this.messagesRendered].sort(function (a, b) { + return a.timestamp + - b.timestamp + }) await this.getUpdateComplete(); scrollElement.scrollIntoView({ behavior: 'auto', block: 'center' }); - return { oldMessages: __msg, scrollElement: scrollElement } - } else if (this._messages.length > 15) { - this.messagesRendered = [...this._messages.splice(this._messages.length - 15), ...this.messagesRendered] - await this.getUpdateComplete(); - scrollElement.scrollIntoView({ behavior: 'auto', block: 'center' }); - return { oldMessages: this._messages.splice(this._messages.length - 15), scrollElement: scrollElement } } else { + const getInitialMessages = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=20&reverse=true&before=${scrollElement.messageObj.timestamp}`, + }); + + + const decodeMsgs = getInitialMessages.map((eachMessage) => { + + return this.decodeMessage(eachMessage) + + + + + + }) + + this.messagesRendered = [...decodeMsgs, ...this.messagesRendered].sort(function (a, b) { + return a.timestamp + - b.timestamp + }) + await this.getUpdateComplete(); + + scrollElement.scrollIntoView({ behavior: 'auto', block: 'center' }); - return false } + } async processMessages(messages, isInitial) { @@ -344,7 +400,7 @@ class ChatPage extends LitElement { } // TODO: Determine number of initial messages by screen height... - this._messages.length <= 15 ? adjustMessages() : this._initialMessages = this._messages.splice(this._messages.length - 15); + this._initialMessages = this._messages this.messagesRendered = this._initialMessages @@ -423,27 +479,27 @@ class ChatPage extends LitElement { } } - async renderNewMessage(newMessage) { + async renderNewMessage(newMessage) { const viewElement = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement'); if (newMessage.sender === this.selectedAddress.address) { - this.messagesRendered = [...this.messagesRendered, newMessage] - await this.getUpdateComplete(); + this.messagesRendered = [...this.messagesRendered, newMessage] + await this.getUpdateComplete(); viewElement.scrollTop = viewElement.scrollHeight; } else if (this.isUserDown) { // Append the message and scroll to the bottom if user is down the page - this.messagesRendered = [...this.messagesRendered, newMessage] - await this.getUpdateComplete(); + this.messagesRendered = [...this.messagesRendered, newMessage] + await this.getUpdateComplete(); viewElement.scrollTop = viewElement.scrollHeight; } else { - this.messagesRendered = [...this.messagesRendered, newMessage] - await this.getUpdateComplete(); + this.messagesRendered = [...this.messagesRendered, newMessage] + await this.getUpdateComplete(); this.showNewMesssageBar(); } @@ -487,7 +543,7 @@ class ChatPage extends LitElement { async fetchChatMessages(chatId) { - const initDirect = (cid) => { + const initDirect = async (cid) => { let initial = 0 @@ -507,6 +563,9 @@ class ChatPage extends LitElement { directSocketLink = `ws://${nodeUrl}/websockets/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}`; } + + + const directSocket = new WebSocket(directSocketLink); // Open Connection @@ -516,13 +575,37 @@ class ChatPage extends LitElement { } // Message Event - directSocket.onmessage = (e) => { + directSocket.onmessage = async (e) => { if (initial === 0) { + const isReceipient = this.chatId.includes('direct') + + + const chatReference1 = isReceipient ? 'direct' : 'group'; + const chatReference2 = this.chatId.split('/')[1]; + const cachedData = await messagesCache.getItem(`${chatReference1}-${chatReference2}`); + + let getInitialMessages = [] + if (cachedData && cachedData.length !== 0) { + const lastMessage = cachedData[cachedData.length - 1] + const newMessages = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=20&reverse=true&after=${lastMessage.timestamp}`, + }); + getInitialMessages = [...cachedData, ...newMessages] + } else { + getInitialMessages = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=20&reverse=true`, + }); + + + } + + this.processMessages(getInitialMessages, true) - this.isLoadingMessages = true - this.processMessages(JSON.parse(e.data), true) initial = initial + 1 + } else { this.processMessages(JSON.parse(e.data), false) @@ -578,12 +661,38 @@ class ChatPage extends LitElement { } // Message Event - groupSocket.onmessage = (e) => { + groupSocket.onmessage = async (e) => { if (initial === 0) { + const isGroup = this.chatId.includes('group') + const chatReference1 = isGroup ? 'group' : 'direct'; + const chatReference2 = this.chatId.split('/')[1]; + + const cachedData = await messagesCache.getItem(`${chatReference1}-${chatReference2}`); + + let getInitialMessages = [] + if (cachedData && cachedData.length !== 0) { + + const lastMessage = cachedData[cachedData.length - 1] + + const newMessages = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?txGroupId=${groupId}&limit=20&reverse=true&after=${lastMessage.timestamp}`, + }); + + getInitialMessages = [...cachedData, ...newMessages] + } else { + getInitialMessages = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?txGroupId=${groupId}&limit=20&reverse=true`, + }); + + + } + + + this.processMessages(getInitialMessages, true) - this.isLoadingMessages = true - this.processMessages(JSON.parse(e.data), true) initial = initial + 1 } else { From 2a3da268081819befa2fd0172f94e223d5f356d8 Mon Sep 17 00:00:00 2001 From: Justin Ferrari <‘justinwesleyferrari@gmail.com’> Date: Sun, 2 Oct 2022 10:29:31 -0500 Subject: [PATCH 004/123] Finished design of the replies --- .../plugins/core/components/ChatPage.js | 130 ++++++++++++++---- .../core/components/ChatScroller-css.js | 36 ++++- .../plugins/core/components/ChatScroller.js | 36 +++-- 3 files changed, 156 insertions(+), 46 deletions(-) diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index d572a6cb..52a09501 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -46,7 +46,7 @@ class ChatPage extends LitElement { hideNewMesssageBar: { attribute: false }, chatEditorPlaceholder: { type: String }, messagesRendered: { type: Array }, - repliedToSignature: { type: String }, + repliedToMessageObj: { type: Object }, } } @@ -64,12 +64,11 @@ class ChatPage extends LitElement { .chat-text-area .typing-area { display: flex; - flex-direction: row; + flex-direction: column; position: absolute; bottom: 0; width: 98%; box-sizing: border-box; - padding: 5px; margin-bottom: 8px; border: 1px solid var(--black); border-radius: 10px; @@ -91,10 +90,67 @@ class ChatPage extends LitElement { border: none; } + .repliedTo-container { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 10px 20px 8px 10px; + } + + .repliedTo-subcontainer { + display: flex; + flex-direction: row; + align-items: center; + gap: 15px; + } + + .repliedTo-message { + display: flex; + flex-direction: column; + gap: 5px; + } + + + .senderName { + margin: 0; + color: var(--mdc-theme-primary); + font-weight: bold; + } + + .original-message { + margin: 0; + } + + .reply-icon { + width: 20px; + color: var(--mdc-theme-primary); + } + + .close-icon { + color: #676b71; + width: 25px; + transition: all 0.1s ease-in-out; + } + + .close-icon:hover { + cursor: pointer; + color: #494c50; + } + + .chat-text-area .typing-area .chatbar { + width: auto; + display: flex; + justify-content: center; + align-items: center; + height: auto; + padding: 5px; + } + .chat-text-area .typing-area .emoji-button { width: 45px; height: 40px; - padding: 5px; + padding-top: 4px; border: none; outline: none; background: transparent; @@ -136,20 +192,38 @@ class ChatPage extends LitElement { this.isPasteMenuOpen = false this.chatEditorPlaceholder = this.renderPlaceholder() this.messagesRendered = [] - this.repliedToSignature = '' + this.repliedToMessageObj = null } - + render() { - return html` ${this.isLoadingMessages ? html`

${translate("chatpage.cchange22")}

` : this.renderChatScroller(this._initialMessages)}
-
- - - +
+ ${this.repliedToMessageObj && html` +
+
+ +
+

${this.repliedToMessageObj.senderName ? this.repliedToMessageObj.senderName : this.repliedToMessageObj.sender}

+

${this.repliedToMessageObj.decodedMessage}

+
+
+ this.closeRepliedToContainer()} + > +
+ `} +
+ + + +
` @@ -291,8 +365,8 @@ class ChatPage extends LitElement { .emojiPicker=${this.emojiPicker} .escapeHTML=${escape} .getOldMessage=${this.getOldMessage} - .setRepliedToSignature=${this.setRepliedToSignature} - .repliedToSignature=${this.repliedToSignature} + .setRepliedToMessageObj=${(val) => this.setRepliedToMessageObj(val)} + .focusChatEditor=${() => this.focusChatEditor()} > ` } @@ -397,10 +471,22 @@ class ChatPage extends LitElement { } } + + async setRepliedToMessageObj(messageObj) { + console.log(messageObj, "Replied To Message Object Here") + this.repliedToMessageObj = {...messageObj}; + this.requestUpdate(); + await this.updateComplete; + console.log(this.repliedToMessageObj); + } - setRepliedToSignature(messageSignature) { - console.log(messageSignature, "Replied To Message Signature Here") - this.repliedToSignature = messageSignature; + closeRepliedToContainer() { + this.repliedToMessageObj = null; + this.requestUpdate(); + } + + focusChatEditor() { + this.chatEditor.focus(); } /** @@ -454,10 +540,6 @@ class ChatPage extends LitElement { async renderNewMessage(newMessage) { const viewElement = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement'); - const downObserver = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('downObserver'); - const li = document.createElement('li'); - li.innerHTML = this.chatMessageTemplate(newMessage); - li.id = newMessage.signature; if (newMessage.sender === this.selectedAddress.address) { @@ -683,11 +765,11 @@ class ChatPage extends LitElement { this.chatEditor.enable(); let err1string = get("chatpage.cchange24"); parentEpml.request('showSnackBar', `${err1string}`); - } else if(this.repliedToSignature) { + } else if (this.repliedToMessageObj) { const messageObject = { messageText, images: [''], - repliedTo: this.repliedToSignature, + repliedTo: this.repliedToMessageObj.signature, version: 1 } const stringifyMessageObject = JSON.stringify(messageObject) diff --git a/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js b/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js index 898144a8..1be44de6 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js +++ b/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js @@ -17,6 +17,10 @@ export const chatStyles = css` --mdc-theme-secondary: var(--mdc-theme-primary); } + * :focus-visible { + outline: none; + } + *::-webkit-scrollbar-track { background: var(--scrollbarBG); } @@ -110,6 +114,8 @@ export const chatStyles = css` .original-message { position: relative; + display: flex; + flex-direction: column; color: black; line-height: 19px; overflow-wrap: break-word; @@ -120,8 +126,6 @@ export const chatStyles = css` background-color: rgba(209, 209, 209, 0.79); padding: 8px 5px 8px 25px; white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; } .original-message:before { @@ -131,7 +135,18 @@ export const chatStyles = css` left: 10px; height: 75%; width: 2.6px; - background-color: rgb(14, 190, 240); + background-color: var(--mdc-theme-primary); + } + + .original-message-sender { + margin: 0 0 5px 0; + color: var(--mdc-theme-primary); + } + + .replied-message { + margin: 0; + overflow: hidden; + text-overflow: ellipsis; } .message { @@ -166,7 +181,7 @@ export const chatStyles = css` display: none; position: absolute; top: -38px; - right: 20px; + right: 25px; } .emoji { @@ -229,6 +244,10 @@ export const chatStyles = css` position: relative; } + .container:focus-visible { + outline: none; + } + .menu-icon { width: 100%; padding: 5px; @@ -238,6 +257,7 @@ export const chatStyles = css` } .menu-icon:hover { + border-radius: 5px; background-color: #dad9d9; transition: all 0.1s ease-in-out; cursor: pointer; @@ -249,11 +269,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; @@ -262,7 +283,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 { @@ -285,7 +307,7 @@ export const chatStyles = css` } .block-user-container { - display: block; + display: none; position: absolute; left: -5px; } diff --git a/qortal-ui-plugins/plugins/core/components/ChatScroller.js b/qortal-ui-plugins/plugins/core/components/ChatScroller.js index f8a05c56..b8b52c9f 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatScroller.js +++ b/qortal-ui-plugins/plugins/core/components/ChatScroller.js @@ -25,8 +25,8 @@ class ChatScroller extends LitElement { initialMessages: { type: Array }, // First set of messages to load.. 15 messages max ( props ) messages: { type: Array }, hideMessages: { type: Array }, - setRepliedToSignature: {type: Function}, - repliedToSignature: {type: String}, + setRepliedToMessageObj: { type: Function }, + focusChatEditor: { type: Function } } } @@ -55,8 +55,8 @@ class ChatScroller extends LitElement { .escapeHTML=${this.escapeHTML} .messageObj=${message} .hideMessages=${this.hideMessages} - .setRepliedToSignature=${this.setRepliedToSignature} - .repliedToSignature=${this.repliedToSignature} + .setRepliedToMessageObj=${this.setRepliedToMessageObj} + .focusChatEditor=${this.focusChatEditor} > ` )} @@ -149,8 +149,8 @@ class MessageTemplate extends LitElement { openDialogPrivateMessage: {type: Boolean}, openDialogBlockUser: {type: Boolean}, showBlockAddressIcon: { type: Boolean }, - setRepliedToSignature: {type: Function}, - repliedToSignature: {type: String}, + setRepliedToMessageObj: { type: Function }, + focusChatEditor: { type: Function }, } } @@ -193,7 +193,6 @@ class MessageTemplate extends LitElement { } render() { - console.log(this.messageObj, "here244") const hidemsg = this.hideMessages let message = "" let repliedToData = null @@ -252,7 +251,8 @@ class MessageTemplate extends LitElement {
- ${repliedToData.decodedMessage} +

${repliedToData.sendName ?? repliedToData.sender}

+

${repliedToData.decodedMessage}

`}
@@ -267,9 +267,9 @@ class MessageTemplate extends LitElement { .showBlockUserModal=${() => this.showBlockUserModal()} .showBlockIconFunc=${(props) => this.showBlockIconFunc(props)} .showBlockAddressIcon=${this.showBlockAddressIcon} - .originalMessageSignature=${this.messageObj.signature} - .setRepliedToSignature=${this.setRepliedToSignature} - .repliedToSignature=${this.repliedToSignature} + .originalMessage=${this.messageObj} + .setRepliedToMessageObj=${this.setRepliedToMessageObj} + .focusChatEditor=${this.focusChatEditor} @blur=${() => this.showBlockIconFunc(false)} > @@ -300,9 +300,9 @@ class ChatMenu extends LitElement { toblockaddress: { type: String, attribute: true }, showBlockIconFunc: {type: Function}, showBlockAddressIcon: {type: Boolean}, - originalMessageSignature: {type: String}, - setRepliedToSignature: {type: Function}, - repliedToSignature: {type: String}, + originalMessage: {type: Object}, + setRepliedToMessageObj: { type: Function }, + focusChatEditor: {type: Function}, } } @@ -337,7 +337,13 @@ class ChatMenu extends LitElement { -
@@ -340,6 +379,13 @@ class ChatPage extends LitElement { parentEpml.imReady(); } + updated(changedProperties) { + if (changedProperties && changedProperties.has('editedMessageObj')) { + console.log('yo123') + this.chatEditor.insertText(this.editedMessageObj.decodedMessage) + } + } + changeLanguage() { const checkLanguage = localStorage.getItem('qortalLanguage') @@ -366,6 +412,7 @@ class ChatPage extends LitElement { .escapeHTML=${escape} .getOldMessage=${this.getOldMessage} .setRepliedToMessageObj=${(val) => this.setRepliedToMessageObj(val)} + .setEditedMessageObj=${(val) => this.setEditedMessageObj(val)} .focusChatEditor=${() => this.focusChatEditor()} > ` @@ -471,15 +518,31 @@ class ChatPage extends LitElement { } } + + // set replied to message in chat editor - async setRepliedToMessageObj(messageObj) { - console.log(messageObj, "Replied To Message Object Here") + setRepliedToMessageObj(messageObj) { this.repliedToMessageObj = {...messageObj}; + this.editedMessageObj = null; this.requestUpdate(); - await this.updateComplete; - console.log(this.repliedToMessageObj); } + // set edited message in chat editor + + setEditedMessageObj(messageObj) { + console.log(messageObj, "Edited Message Object Here") + this.editedMessageObj = {...messageObj}; + this.repliedToMessageObj = null; + this.requestUpdate(); + console.log(this.editedMessageObj); + } + + closeEditMessageContainer() { + this.editedMessageObj = null; + this.chatEditor.resetValue(); + this.requestUpdate(); + } + closeRepliedToContainer() { this.repliedToMessageObj = null; this.requestUpdate(); diff --git a/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js b/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js index 1be44de6..3e56949f 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js +++ b/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js @@ -181,7 +181,7 @@ export const chatStyles = css` display: none; position: absolute; top: -38px; - right: 25px; + right: 5px; } .emoji { @@ -235,12 +235,10 @@ export const chatStyles = css` display: flex; flex-direction: row; align-items: center; - gap: 5px; background-color: white; border: 1px solid #dad9d9; border-radius: 5px; height:100%; - width: 100px; position: relative; } @@ -250,7 +248,7 @@ export const chatStyles = css` .menu-icon { width: 100%; - padding: 5px; + padding: 5px 7px; display: flex; align-items: center; font-size: 13px; @@ -307,7 +305,7 @@ export const chatStyles = css` } .block-user-container { - display: none; + display: block; position: absolute; left: -5px; } diff --git a/qortal-ui-plugins/plugins/core/components/ChatScroller.js b/qortal-ui-plugins/plugins/core/components/ChatScroller.js index b8b52c9f..14d314be 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatScroller.js +++ b/qortal-ui-plugins/plugins/core/components/ChatScroller.js @@ -26,6 +26,7 @@ class ChatScroller extends LitElement { messages: { type: Array }, hideMessages: { type: Array }, setRepliedToMessageObj: { type: Function }, + setEditedMessageObj: { type: Function }, focusChatEditor: { type: Function } } } @@ -56,6 +57,7 @@ class ChatScroller extends LitElement { .messageObj=${message} .hideMessages=${this.hideMessages} .setRepliedToMessageObj=${this.setRepliedToMessageObj} + .setEditedMessageObj=${this.setEditedMessageObj} .focusChatEditor=${this.focusChatEditor} > ` @@ -150,6 +152,7 @@ class MessageTemplate extends LitElement { openDialogBlockUser: {type: Boolean}, showBlockAddressIcon: { type: Boolean }, setRepliedToMessageObj: { type: Function }, + setEditedMessageObj: { type: Function }, focusChatEditor: { type: Function }, } } @@ -258,21 +261,23 @@ class MessageTemplate extends LitElement {
${unsafeHTML(this.emojiPicker.parse(this.escapeHTML(message)))}
- this.showPrivateMessageModal()} - .showBlockUserModal=${() => this.showBlockUserModal()} - .showBlockIconFunc=${(props) => this.showBlockIconFunc(props)} - .showBlockAddressIcon=${this.showBlockAddressIcon} - .originalMessage=${this.messageObj} - .setRepliedToMessageObj=${this.setRepliedToMessageObj} - .focusChatEditor=${this.focusChatEditor} - @blur=${() => this.showBlockIconFunc(false)} - > - + this.showPrivateMessageModal()} + .showBlockUserModal=${() => this.showBlockUserModal()} + .showBlockIconFunc=${(props) => this.showBlockIconFunc(props)} + .showBlockAddressIcon=${this.showBlockAddressIcon} + .originalMessage=${this.messageObj} + .setRepliedToMessageObj=${this.setRepliedToMessageObj} + .setEditedMessageObj=${this.setEditedMessageObj} + .focusChatEditor=${this.focusChatEditor} + .myAddress=${this.myAddress} + @blur=${() => this.showBlockIconFunc(false)} + > +
{}; this.showBlockUserModal = () => {}; } @@ -338,14 +343,27 @@ class ChatMenu extends LitElement {
+ ${this.myAddress === this.originalMessage.sender ? ( + html` + + ` + ) : html`
`} From 912bfffabd95c66df47b23943de2b63942960d18 Mon Sep 17 00:00:00 2001 From: Phillip Lang Martinez Date: Mon, 24 Oct 2022 18:33:02 +0300 Subject: [PATCH 007/123] rough draft reply logic --- qortal-ui-crypto/api/constants.js | 2 +- .../api/transactions/chat/ChatTransaction.js | 5 +- .../plugins/core/components/ChatPage.js | 125 ++++++++++++++++-- .../plugins/core/components/ChatScroller.js | 27 ++-- 4 files changed, 131 insertions(+), 28 deletions(-) diff --git a/qortal-ui-crypto/api/constants.js b/qortal-ui-crypto/api/constants.js index b3fa7cd4..6193f595 100644 --- a/qortal-ui-crypto/api/constants.js +++ b/qortal-ui-crypto/api/constants.js @@ -249,7 +249,7 @@ const ERROR_CODES = { 1000: "Not yet released." } -const CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP = 9999999999999 +const CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP = 0 const QORT_DECIMALS = 1e8 diff --git a/qortal-ui-crypto/api/transactions/chat/ChatTransaction.js b/qortal-ui-crypto/api/transactions/chat/ChatTransaction.js index a53a0f81..d368e8ca 100644 --- a/qortal-ui-crypto/api/transactions/chat/ChatTransaction.js +++ b/qortal-ui-crypto/api/transactions/chat/ChatTransaction.js @@ -82,16 +82,17 @@ export default class ChatTransaction extends ChatBase { this._isText, this._feeBytes ) - + console.log('updated test') // After the feature trigger timestamp we need to include chat reference if (new Date(this._timestamp).getTime() >= CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP) { params.push(this._hasChatReference) if (this._hasChatReference[0] == 1) { + console.log('past through', this._chatReference) params.push(this._chatReference) } } - + console.log({params}) return params; } } diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index 59a784da..0ce9de05 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -503,9 +503,36 @@ class ChatPage extends LitElement { async processMessages(messages, isInitial) { + const findNewMessages = messages.map(async(msg)=> { + console.log({msg}) + let msgItem = msg + try { + const response = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?chatreference=${msg.reference}&reverse=true&involving=${msg.recipient}&involving=${msg.sender}`, + }); + + if(response && Array.isArray(response) && response.length !== 0){ + let responseItem = {...response[0]} + delete responseItem.timestamp + msgItem = { + ...msg, + ...responseItem, + editedTimestamp : response[0].timestamp + } + } + console.log({response}) + } catch (error) { + console.log(error) + } + + return msgItem + }) + const updateMessages = await Promise.all(findNewMessages) + if (isInitial) { - this.messages = messages.map((eachMessage) => { + const decodedMessages = updateMessages.map((eachMessage) => { if (eachMessage.isText === true) { this.messageSignature = eachMessage.signature @@ -518,7 +545,61 @@ class ChatPage extends LitElement { } }) - this._messages = [...this.messages].sort(function (a, b) { + + const findNewMessages2 = decodedMessages.map(async(msg)=> { + console.log({msg}) + let parsedMessageObj = msg + try { + parsedMessageObj = JSON.parse(msg.decodedMessage) + } catch (error) { + return msg + } + + + let msgItem = msg + try { + if(parsedMessageObj.repliedTo){ + const response = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?chatreference=${parsedMessageObj.repliedTo}&reverse=true&involving=${msg.recipient}&involving=${msg.sender}`, + }); + + if(response && Array.isArray(response) && response.length !== 0){ + + msgItem = { + ...msg, + repliedToData : this.decodeMessage(response[0]) + } + } else { + + const response2 = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?reference=${parsedMessageObj.repliedTo}&reverse=true&involving=${msg.recipient}&involving=${msg.sender}`, + }); + + if(response2 && Array.isArray(response2) && response2.length !== 0){ + + msgItem = { + ...msg, + repliedToData : this.decodeMessage(response2[0]) + } + } + } + + } + + + } catch (error) { + console.log(error) + } + + return msgItem + }) + const updateMessages2 = await Promise.all(findNewMessages2) + + + + this._messages = updateMessages2.sort(function (a, b) { return a.timestamp - b.timestamp }) @@ -905,7 +986,8 @@ class ChatPage extends LitElement { // need whole original message object, transform the data and put it in local storage // create new var called repliedToData and use that to modify the UI // find specific object property in local - + let typeMessage = 'regular'; + this.isLoading = true; this.chatEditor.disable(); const messageText = this.mirrorChatInput.value; @@ -914,6 +996,8 @@ class ChatPage extends LitElement { const sanitizedMessage = messageText.replace(/ /gi, ' ').replace(//gi, '\n'); const trimmedMessage = sanitizedMessage.trim(); + console.log('is replied', this.repliedToMessageObj) + if (/^\s*$/.test(trimmedMessage)) { this.isLoading = false; this.chatEditor.enable(); @@ -923,14 +1007,35 @@ class ChatPage extends LitElement { let err1string = get("chatpage.cchange24"); parentEpml.request('showSnackBar', `${err1string}`); } else if (this.repliedToMessageObj) { + let chatReference = this.repliedToMessageObj.reference + + if(this.repliedToMessageObj.chatReference){ + chatReference = this.repliedToMessageObj.chatReference + } + typeMessage = 'reply' const messageObject = { messageText, images: [''], - repliedTo: this.repliedToMessageObj.signature, + repliedTo: chatReference, version: 1 } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject); + this.sendMessage(stringifyMessageObject, typeMessage ); + } else if (this.editedMessageObj) { + let chatReference = this.repliedToMessageObj.reference + + if(this.repliedToMessageObj.chatReference){ + chatReference = this.repliedToMessageObj.chatReference + } + typeMessage = 'reply' + const messageObject = { + messageText, + images: [''], + repliedTo: chatReference, + version: 1 + } + const stringifyMessageObject = JSON.stringify(messageObject) + this.sendMessage(stringifyMessageObject, typeMessage, chatReference); } else { const messageObject = { messageText, @@ -939,11 +1044,12 @@ class ChatPage extends LitElement { version: 1 } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject); + this.sendMessage(stringifyMessageObject, typeMessage); } } - async sendMessage(messageText) { + async sendMessage(messageText, typeMessage, chatReference) { + console.log({messageText}, 'hello') this.isLoading = true; let _reference = new Uint8Array(64); @@ -959,7 +1065,8 @@ class ChatPage extends LitElement { timestamp: Date.now(), recipient: this._chatId, recipientPublicKey: this._publicKey.key, - hasChatReference: 0, + hasChatReference: typeMessage === 'edit' ? 1 : 0, + chatReference: chatReference, message: messageText, lastReference: reference, proofOfWorkNonce: 0, @@ -967,7 +1074,7 @@ class ChatPage extends LitElement { isText: 1 } }); - + console.log({chatResponse}) _computePow(chatResponse) } else { let groupResponse = await parentEpml.request('chat', { diff --git a/qortal-ui-plugins/plugins/core/components/ChatScroller.js b/qortal-ui-plugins/plugins/core/components/ChatScroller.js index 14d314be..db991014 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatScroller.js +++ b/qortal-ui-plugins/plugins/core/components/ChatScroller.js @@ -44,6 +44,7 @@ class ChatScroller extends LitElement { render() { + console.log({messages: this.messages}) return html`
    @@ -202,20 +203,7 @@ class MessageTemplate extends LitElement { try { const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage) message = parsedMessageObj.messageText - repliedToData = { - "timestamp": 1663419371885, - "txGroupId": 0, - "reference": "5LuncmE2RsGVdQizkZLnjgqU8QozR2hHhkiSujUgywEfqAvm6RW4xZ7c9XjuMnb76bNmX2ntRNhnBF4ErvawM1dW", - "senderPublicKey": "xmZXCYzGU2t3S6Ehm2zp4pVm83q9d143NKRgmiU1dXW", - "sender": "Qj9aLrdK2FLQY6YssRQUkDmXNJCko2zF7e", - "senderName": "GiseleH", - "data": "3JLP9vViLoRPJ1Pqt2uC6Ufqf7wgTrs4HuV4Ltgwdnf5ekcBCCf5MTm2Sg3sXHeuVnCpoJAyVdqgAbr7tcBoq3soNZTjteusXjjW3NSMNcJEAadaTYC68xGXGmvK1jRyioPqGaKiXKzR2jPPRV5SyiPd66788Z2Rqt3VQB98rvronX5w5tE9UUWRor6bmMeVL3dj7fHYhLPPE5VBpCS9Eskti7vnTgDUQxnjfr", - "isText": true, - "isEncrypted": false, - "signature": "3jWvhQKSDt4Zqeup5sLfyNksVVphFW5iF11PsTZzXQLCCPH9pDMqwNoKE2oe3DPYz47VbbLgJaAWMVA44z9dUr9U", - "decodedMessage": "for TrentB512 who computer crashed your registered name in qortal for your level 3 account was TrentB512 https://exqlorer.com/address/Qf58otnEXeoyvD7dvYmfEGpQ64oMD3uvwM" - - } + repliedToData = this.messageObj.repliedToData } catch (error) { message = this.messageObj.decodedMessage } @@ -238,7 +226,14 @@ class MessageTemplate extends LitElement { } else { nameMenu = html`${this.messageObj.senderName ? this.messageObj.senderName : this.messageObj.sender}` } - + if(repliedToData){ + try { + const parsedMsg = JSON.parse(repliedToData.decodedMessage) + repliedToData.decodedMessage = parsedMsg + } catch (error) { + + } + } return hideit ? html`
  • ` : html`
  • @@ -255,7 +250,7 @@ class MessageTemplate extends LitElement { class="original-message" style=${this.messageObj.sender === this.myAddress && "background-color: rgb(179 179 179 / 79%)"}>

    ${repliedToData.sendName ?? repliedToData.sender}

    -

    ${repliedToData.decodedMessage}

    +

    ${repliedToData.decodedMessage.messageText}

    `}
    From a7ef3f01ca83a009ac4ef8661e6240a0aadda95d Mon Sep 17 00:00:00 2001 From: Phillip Lang Martinez Date: Wed, 26 Oct 2022 10:55:59 +0300 Subject: [PATCH 008/123] update msg formats --- .../plugins/core/components/ChatModals.js | 10 +- .../plugins/core/components/ChatPage.js | 150 ++++++++++++------ .../plugins/core/components/ChatScroller.js | 6 +- .../core/components/ChatWelcomePage.js | 10 +- .../plugins/core/components/NameMenu.js | 10 +- .../core/messaging/q-chat/q-chat.src.js | 10 +- 6 files changed, 141 insertions(+), 55 deletions(-) diff --git a/qortal-ui-plugins/plugins/core/components/ChatModals.js b/qortal-ui-plugins/plugins/core/components/ChatModals.js index ab772335..b7370ef3 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatModals.js +++ b/qortal-ui-plugins/plugins/core/components/ChatModals.js @@ -175,7 +175,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 +190,7 @@ class ChatModals extends LitElement { recipient: recipient, recipientPublicKey: _publicKey, hasChatReference: 0, - message: messageText, + message: stringifyMessageObject, lastReference: reference, proofOfWorkNonce: 0, isEncrypted: isEncrypted, diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index 0ce9de05..36046bf9 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -221,7 +221,7 @@ class ChatPage extends LitElement {

    ${this.repliedToMessageObj.senderName ? this.repliedToMessageObj.senderName : this.repliedToMessageObj.sender}

    -

    ${this.repliedToMessageObj.decodedMessage}

    +

    ${this.repliedToMessageObj.message}

    ${translate("chatpage.cchange25")}

    -

    ${this.editedMessageObj.decodedMessage}

    +

    ${this.editedMessageObj.message}

{ @@ -475,7 +474,7 @@ class ChatPage extends LitElement { } else { const getInitialMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=20&reverse=true&before=${scrollElement.messageObj.timestamp}`, + url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=20&reverse=true&before=${scrollElement.messageObj.timestamp}&haschatreference=false`, }); @@ -504,7 +503,7 @@ class ChatPage extends LitElement { async processMessages(messages, isInitial) { const findNewMessages = messages.map(async(msg)=> { - console.log({msg}) + let msgItem = msg try { const response = await parentEpml.request('apiCall', { @@ -521,7 +520,7 @@ class ChatPage extends LitElement { editedTimestamp : response[0].timestamp } } - console.log({response}) + } catch (error) { console.log(error) } @@ -547,7 +546,7 @@ class ChatPage extends LitElement { const findNewMessages2 = decodedMessages.map(async(msg)=> { - console.log({msg}) + let parsedMessageObj = msg try { parsedMessageObj = JSON.parse(msg.decodedMessage) @@ -622,15 +621,58 @@ class ChatPage extends LitElement { setTimeout(() => this.downElementObserver(), 500) } else { - messages.forEach((eachMessage) => { - - const _eachMessage = this.decodeMessage(eachMessage) - - - this.renderNewMessage(_eachMessage) + + const findNewMessages2 = messages.map(async(msg)=> { + const _eachMessage = this.decodeMessage(msg) + let parsedMessageObj = _eachMessage + try { + parsedMessageObj = JSON.parse(_eachMessage.decodedMessage) + } catch (error) { + return msg + } + + + let msgItem = _eachMessage + try { + if(parsedMessageObj.repliedTo){ + const response = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?chatreference=${parsedMessageObj.repliedTo}&reverse=true&involving=${_eachMessage.recipient}&involving=${_eachMessage.sender}`, + }); + + if(response && Array.isArray(response) && response.length !== 0){ + + msgItem = { + ..._eachMessage, + repliedToData : this.decodeMessage(response[0]) + } + } else { + const response2 = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?reference=${parsedMessageObj.repliedTo}&reverse=true&involving=${_eachMessage.recipient}&involving=${_eachMessage.sender}`, + }); + if(response2 && Array.isArray(response2) && response2.length !== 0){ + + msgItem = { + ..._eachMessage, + repliedToData : this.decodeMessage(response2[0]) + } + } + } + + } + + + } catch (error) { + console.log(error) + } + + + this.renderNewMessage(msgItem) }) + await Promise.all(findNewMessages2) // this.newMessages = this.newMessages.concat(_newMessages) @@ -654,11 +696,9 @@ class ChatPage extends LitElement { // set edited message in chat editor setEditedMessageObj(messageObj) { - console.log(messageObj, "Edited Message Object Here") this.editedMessageObj = {...messageObj}; this.repliedToMessageObj = null; this.requestUpdate(); - console.log(this.editedMessageObj); } closeEditMessageContainer() { @@ -725,7 +765,17 @@ class ChatPage extends LitElement { } async renderNewMessage(newMessage) { + if(newMessage.chatReference){ + const findOriginalMessageIndex = this.messagesRendered.findIndex(msg=> msg.reference === newMessage.chatReference || (msg.chatReference && msg.chatReference === newMessage.chatReference) ) + if(findOriginalMessageIndex !== -1){ + const newMessagesRendered = [...this.messagesRendered] + newMessagesRendered[findOriginalMessageIndex] = {...newMessage, timestamp: newMessagesRendered[findOriginalMessageIndex].timestamp, editedTimestamp: newMessage.timestamp } + this.messagesRendered = newMessagesRendered + await this.getUpdateComplete(); + } + return + } const viewElement = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement'); if (newMessage.sender === this.selectedAddress.address) { @@ -761,11 +811,11 @@ class ChatPage extends LitElement { if (this.isReceipient === true) { // direct chat - if (encodedMessageObj.isEncrypted === true && this._publicKey.hasPubKey === true) { + if (encodedMessageObj.isEncrypted === true && this._publicKey.hasPubKey === true && encodedMessageObj.data) { let decodedMessage = window.parent.decryptChatMessage(encodedMessageObj.data, window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey, this._publicKey.key, encodedMessageObj.reference) decodedMessageObj = { ...encodedMessageObj, decodedMessage } - } else if (encodedMessageObj.isEncrypted === false) { + } else if (encodedMessageObj.isEncrypted === false && encodedMessageObj.data) { let bytesArray = window.parent.Base58.decode(encodedMessageObj.data) let decodedMessage = new TextDecoder('utf-8').decode(bytesArray) @@ -821,7 +871,6 @@ class ChatPage extends LitElement { // Message Event directSocket.onmessage = async (e) => { - if (initial === 0) { const isReceipient = this.chatId.includes('direct') @@ -835,13 +884,13 @@ class ChatPage extends LitElement { const lastMessage = cachedData[cachedData.length - 1] const newMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=20&reverse=true&after=${lastMessage.timestamp}`, + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=20&reverse=true&after=${lastMessage.timestamp}&haschatreference=false`, }); getInitialMessages = [...cachedData, ...newMessages] } else { getInitialMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=20&reverse=true`, + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=20&reverse=true&haschatreference=false`, }); @@ -852,8 +901,10 @@ class ChatPage extends LitElement { initial = initial + 1 } else { - - this.processMessages(JSON.parse(e.data), false) + if(e.data){ + this.processMessages(JSON.parse(e.data), false) + } + } } @@ -921,14 +972,14 @@ class ChatPage extends LitElement { const newMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?txGroupId=${groupId}&limit=20&reverse=true&after=${lastMessage.timestamp}`, + url: `/chat/messages?txGroupId=${groupId}&limit=20&reverse=true&after=${lastMessage.timestamp}&haschatreference=false`, }); getInitialMessages = [...cachedData, ...newMessages] } else { getInitialMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?txGroupId=${groupId}&limit=20&reverse=true`, + url: `/chat/messages?txGroupId=${groupId}&limit=20&reverse=true&haschatreference=false`, }); @@ -939,8 +990,10 @@ class ChatPage extends LitElement { initial = initial + 1 } else { - - this.processMessages(JSON.parse(e.data), false) + if(e.data){ + this.processMessages(JSON.parse(e.data), false) + } + } } @@ -991,12 +1044,11 @@ class ChatPage extends LitElement { this.isLoading = true; this.chatEditor.disable(); const messageText = this.mirrorChatInput.value; - // Format and Sanitize Message const sanitizedMessage = messageText.replace(/ /gi, ' ').replace(//gi, '\n'); const trimmedMessage = sanitizedMessage.trim(); - console.log('is replied', this.repliedToMessageObj) + if (/^\s*$/.test(trimmedMessage)) { this.isLoading = false; @@ -1014,7 +1066,7 @@ class ChatPage extends LitElement { } typeMessage = 'reply' const messageObject = { - messageText, + messageText: trimmedMessage, images: [''], repliedTo: chatReference, version: 1 @@ -1022,23 +1074,31 @@ class ChatPage extends LitElement { const stringifyMessageObject = JSON.stringify(messageObject) this.sendMessage(stringifyMessageObject, typeMessage ); } else if (this.editedMessageObj) { - let chatReference = this.repliedToMessageObj.reference + typeMessage = 'edit' + let chatReference = this.editedMessageObj.reference - if(this.repliedToMessageObj.chatReference){ - chatReference = this.repliedToMessageObj.chatReference + if(this.editedMessageObj.chatReference){ + chatReference = this.editedMessageObj.chatReference } - typeMessage = 'reply' + + let message = "" + try { + const parsedMessageObj = JSON.parse(this.editedMessageObj.decodedMessage) + message = parsedMessageObj + + } catch (error) { + message = this.messageObj.decodedMessage + } const messageObject = { - messageText, - images: [''], - repliedTo: chatReference, - version: 1 + ...message, + messageText: trimmedMessage, + } const stringifyMessageObject = JSON.stringify(messageObject) this.sendMessage(stringifyMessageObject, typeMessage, chatReference); } else { const messageObject = { - messageText, + messageText: trimmedMessage, images: [''], repliedTo: '', version: 1 @@ -1049,7 +1109,7 @@ class ChatPage extends LitElement { } async sendMessage(messageText, typeMessage, chatReference) { - console.log({messageText}, 'hello') + this.isLoading = true; let _reference = new Uint8Array(64); @@ -1074,7 +1134,7 @@ class ChatPage extends LitElement { isText: 1 } }); - console.log({chatResponse}) + _computePow(chatResponse) } else { let groupResponse = await parentEpml.request('chat', { diff --git a/qortal-ui-plugins/plugins/core/components/ChatScroller.js b/qortal-ui-plugins/plugins/core/components/ChatScroller.js index db991014..3db65871 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatScroller.js +++ b/qortal-ui-plugins/plugins/core/components/ChatScroller.js @@ -44,7 +44,6 @@ class ChatScroller extends LitElement { render() { - console.log({messages: this.messages}) return html`
- + @@ -259,13 +257,13 @@ class ChatTextEditor extends LitElement { window.addEventListener('storage', () => { const checkTheme = localStorage.getItem('qortalTheme'); - const captionEditor = this.shadowRoot.getElementById(this.iframeId).contentWindow.document.getElementById('testingId') + const chatbar = this.shadowRoot.getElementById(this.iframeId).contentWindow.document.getElementById('testingId'); if (checkTheme === 'dark') { this.theme = 'dark'; - captionEditor.style.cssText = "color:#ffffff;" + chatbar.style.cssText = "color:#ffffff;" } else { this.theme = 'light'; - captionEditor.style.cssText = "color:#080808;" + chatbar.style.cssText = "color:#080808;" } }) @@ -416,7 +414,34 @@ class ChatTextEditor extends LitElement { 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; @@ -433,11 +458,11 @@ class ChatTextEditor extends LitElement { div[contentEditable=true]:empty:before { content: attr(data-placeholder); display: block; - color: rgb(103, 107, 113); text-overflow: ellipsis; overflow: hidden; user-select: none; white-space: nowrap; + opacity: 0.7; } div[contentEditable=false]{ @@ -672,6 +697,7 @@ class ChatTextEditor extends LitElement { 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'); From c97ef405181de38edceb8a418b258b1742d1f982 Mon Sep 17 00:00:00 2001 From: Phillip Lang Martinez Date: Wed, 30 Nov 2022 15:15:15 +0200 Subject: [PATCH 048/123] single emojipicker for chat messages --- .../plugins/core/components/ChatPage.js | 11 +++ .../plugins/core/components/ChatScroller.js | 75 ++++++++++--------- 2 files changed, 50 insertions(+), 36 deletions(-) diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index 5efa10a6..c2d5690b 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -24,6 +24,7 @@ import { replaceMessagesEdited } from '../../utils/replace-messages-edited.js'; import { publishData } from '../../utils/publish-image.js'; import WebWorker from 'web-worker:./computePowWorker.js'; import WebWorkerImage from 'web-worker:./computePowWorkerImage.js'; +import { EmojiPicker } from 'emoji-picker-js'; // const messagesCache = localForage.createInstance({ @@ -550,6 +551,15 @@ class ChatPage extends LitElement { this.uid = new ShortUniqueId() this.userLanguage = "" this.lastMessageRefVisible = false + 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' + }); } render() { @@ -862,6 +872,7 @@ class ChatPage extends LitElement { .focusChatEditor=${() => this.focusChatEditor()} .sendMessage=${(val) => this._sendMessage(val)} .showLastMessageRefScroller=${(val) => this.showLastMessageRefScroller(val)} + .emojiPicker=${this.emojiPicker} > ` diff --git a/qortal-ui-plugins/plugins/core/components/ChatScroller.js b/qortal-ui-plugins/plugins/core/components/ChatScroller.js index 6f534672..4c8c0882 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatScroller.js +++ b/qortal-ui-plugins/plugins/core/components/ChatScroller.js @@ -17,6 +17,7 @@ import { EmojiPicker } from 'emoji-picker-js'; import { cropAddress } from "../../utils/cropAddress"; const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) +let toggledMessage = {} class ChatScroller extends LitElement { static get properties() { return { @@ -30,6 +31,7 @@ class ChatScroller extends LitElement { focusChatEditor: {attribute: false}, sendMessage: {attribute: false}, showLastMessageRefScroller: { type: Function }, + emojiPicker: { attribute: false } } } @@ -41,16 +43,7 @@ class ChatScroller extends LitElement { this._upObserverhandler = this._upObserverhandler.bind(this) this._downObserverHandler = this._downObserverHandler.bind(this) this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address - this.hideMessages = JSON.parse(localStorage.getItem("MessageBlockedAddresses") || "[]") - 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' - }); + this.hideMessages = JSON.parse(localStorage.getItem("MessageBlockedAddresses") || "[]") } @@ -77,7 +70,6 @@ class ChatScroller extends LitElement { } return messageArray; }, []) - return html`
    @@ -98,6 +90,7 @@ class ChatScroller extends LitElement { ?isFirstMessage=${indexMessage === 0} ?isSingleMessageInGroup=${formattedMessage.messages.length > 1} ?isLastMessageInGroup=${indexMessage === formattedMessage.messages.length - 1} + .setToggledMessage=${this.setToggledMessage} > ` ) @@ -119,7 +112,22 @@ class ChatScroller extends LitElement { return true; } + setToggledMessage(message){ + toggledMessage = message + } + + async firstUpdated() { + 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'); @@ -202,6 +210,7 @@ class MessageTemplate extends LitElement { isFirstMessage: { type: Boolean }, isSingleMessageInGroup: { type: Boolean }, isLastMessageInGroup: { type: Boolean }, + setToggledMessage: {attribute: false} } } @@ -241,7 +250,13 @@ class MessageTemplate extends LitElement { } showBlockIconFunc(bool) { - this.shadowRoot.querySelector(".chat-hover").focus({ preventScroll: true }) + const chatHover = this.shadowRoot.querySelector(".chat-hover") + + if(chatHover){ + chatHover.querySelector(".chat-hover").focus({ preventScroll: true }) + } + + if(bool) { this.showBlockAddressIcon = true; } else { @@ -458,6 +473,8 @@ class MessageTemplate extends LitElement { @blur=${() => this.showBlockIconFunc(false)} .sendMessage=${this.sendMessage} version=${version} + .emojiPicker=${this.emojiPicker} + .setToggledMessage=${this.setToggledMessage} >
@@ -533,7 +550,8 @@ class ChatMenu extends LitElement { myAddress: { type: Object }, emojiPicker: { attribute: false }, sendMessage: {attribute: false}, - version: {type: String} + version: {type: String}, + setToggledMessage: {attribute: false} } } @@ -563,30 +581,9 @@ class ChatMenu extends LitElement { parentEpml.request('showSnackBar', `${errorMsg}`) } - firstUpdated () { - 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' - }); - - this.emojiPicker.on('emoji', selection => { - this.sendMessage({ - type: 'reaction', -editedMessageObj: this.originalMessage, - reaction: selection.emoji, - - - }) - }); - } + render() { - console.log('version', this.version) return html`
From 66d94cfc528054142ea4f236f88ab0e8792761a3 Mon Sep 17 00:00:00 2001 From: Phillip Lang Martinez Date: Wed, 30 Nov 2022 16:50:56 +0200 Subject: [PATCH 049/123] fix bounce issue --- .../plugins/core/components/ChatPage.js | 16 +++++++++++++--- .../plugins/utils/replace-messages-edited.js | 10 +--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index c2d5690b..3e6ad48c 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -886,6 +886,7 @@ class ChatPage extends LitElement { } async getOldMessage(scrollElement) { + if (this.isReceipient) { const getInitialMessages = await parentEpml.request('apiCall', { @@ -910,8 +911,12 @@ class ChatPage extends LitElement { - b.timestamp }) await this.getUpdateComplete(); + const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')); - scrollElement.scrollIntoView({ behavior: 'auto', block: 'center' }); + const findElement = marginElements.find((item)=> item.messageObj.reference === scrollElement.messageObj.reference) + if(findElement){ + findElement.scrollIntoView({ behavior: 'auto', block: 'center' }); + } } else { const getInitialMessages = await parentEpml.request('apiCall', { @@ -937,8 +942,13 @@ class ChatPage extends LitElement { - b.timestamp }) await this.getUpdateComplete(); - - scrollElement.scrollIntoView({ behavior: 'auto', block: 'center' }); + const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')); + const findElement = marginElements.find((item)=> item.messageObj.reference === scrollElement.messageObj.reference) + + if(findElement){ + findElement.scrollIntoView({ behavior: 'auto', block: 'center' }); + } + } diff --git a/qortal-ui-plugins/plugins/utils/replace-messages-edited.js b/qortal-ui-plugins/plugins/utils/replace-messages-edited.js index 71579f80..6f727266 100644 --- a/qortal-ui-plugins/plugins/utils/replace-messages-edited.js +++ b/qortal-ui-plugins/plugins/utils/replace-messages-edited.js @@ -17,13 +17,9 @@ export const replaceMessagesEdited = async ({ url: `/chat/messages?chatreference=${msg.reference}&reverse=true${msgQuery}`, }) - console.log({response}) - if (response && Array.isArray(response) && response.length !== 0) { let responseItem = { ...response[0] } - console.log('right before') const decodeResponseItem = decodeMessageFunc(responseItem, isReceipient, _publicKey) - console.log({decodeResponseItem}) delete decodeResponseItem.timestamp msgItem = { ...msg, @@ -43,24 +39,20 @@ export const replaceMessagesEdited = async ({ try { parsedMessageObj = JSON.parse(msg.decodedMessage) } catch (error) { - console.log('error', {parsedMessageObj}) + console.log('error') return msg } - console.log('noerror') let msgItem = msg try { let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}` if (!isReceipient) { msgQuery = `&txGroupId=${msg.txGroupId}` } - - console.log({parsedMessageObj}) if (parsedMessageObj.repliedTo) { const response = await parentEpml.request("apiCall", { type: "api", url: `/chat/messages?chatreference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}`, }) - console.log({response2: response}) if ( response && Array.isArray(response) && From d22a4abdcc6d96d0d10f6a950e4d39c96f2f62e9 Mon Sep 17 00:00:00 2001 From: Phillip Lang Martinez Date: Wed, 30 Nov 2022 17:44:07 +0200 Subject: [PATCH 050/123] add oldmessage fetch loader --- .../plugins/core/components/ChatPage.js | 20 +++++++++++++------ .../core/components/ChatScroller-css.js | 5 +++++ .../plugins/core/components/ChatScroller.js | 13 +++++++++++- .../plugins/core/components/ChatTextEditor.js | 5 +---- 4 files changed, 32 insertions(+), 11 deletions(-) diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index 3e6ad48c..e87a9f67 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -67,6 +67,7 @@ class ChatPage extends LitElement { chatEditorNewChat: { type: Object }, userLanguage: { type: String }, lastMessageRefVisible: { type: Boolean }, + isLoadingOldMessages: {type: Boolean} } } @@ -873,11 +874,15 @@ class ChatPage extends LitElement { .sendMessage=${(val) => this._sendMessage(val)} .showLastMessageRefScroller=${(val) => this.showLastMessageRefScroller(val)} .emojiPicker=${this.emojiPicker} + ?isLoadingMessages=${this.isLoadingOldMessages} + .setIsLoadingMessages=${(val) => this.setIsLoadingMessages(val)} > ` } - + setIsLoadingMessages(val){ + this.isLoadingOldMessages = val + } async getUpdateComplete() { await super.getUpdateComplete(); const marginElements = Array.from(this.shadowRoot.querySelectorAll('chat-scroller')); @@ -886,8 +891,7 @@ class ChatPage extends LitElement { } async getOldMessage(scrollElement) { - - + if (this.isReceipient) { const getInitialMessages = await parentEpml.request('apiCall', { type: 'api', @@ -910,14 +914,17 @@ class ChatPage extends LitElement { return a.timestamp - b.timestamp }) + this.isLoadingOldMessages = false await this.getUpdateComplete(); const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')); const findElement = marginElements.find((item)=> item.messageObj.reference === scrollElement.messageObj.reference) + if(findElement){ findElement.scrollIntoView({ behavior: 'auto', block: 'center' }); } - + + } else { const getInitialMessages = await parentEpml.request('apiCall', { type: 'api', @@ -941,14 +948,15 @@ class ChatPage extends LitElement { return a.timestamp - b.timestamp }) + this.isLoadingOldMessages = false await this.getUpdateComplete(); const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')); const findElement = marginElements.find((item)=> item.messageObj.reference === scrollElement.messageObj.reference) - + if(findElement){ findElement.scrollIntoView({ behavior: 'auto', block: 'center' }); } - + } diff --git a/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js b/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js index 54171ceb..63ca362f 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js +++ b/qortal-ui-plugins/plugins/core/components/ChatScroller-css.js @@ -435,4 +435,9 @@ export const chatStyles = css` align-items: center; height: 100%; } + .spinnerContainer { + display: flex; + width: 100%; + justify-content: center + } ` diff --git a/qortal-ui-plugins/plugins/core/components/ChatScroller.js b/qortal-ui-plugins/plugins/core/components/ChatScroller.js index 4c8c0882..3abb5599 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatScroller.js +++ b/qortal-ui-plugins/plugins/core/components/ChatScroller.js @@ -31,7 +31,9 @@ class ChatScroller extends LitElement { focusChatEditor: {attribute: false}, sendMessage: {attribute: false}, showLastMessageRefScroller: { type: Function }, - emojiPicker: { attribute: false } + emojiPicker: { attribute: false }, + isLoadingMessages: { type: Boolean}, + setIsLoadingMessages: {attribute: false} } } @@ -71,6 +73,11 @@ class ChatScroller extends LitElement { return messageArray; }, []) return html` + ${this.isLoadingMessages ? html` +
+ +
+ ` : ''}
    ${formattedMessages.map((formattedMessage) => { @@ -101,6 +108,9 @@ class ChatScroller extends LitElement { } shouldUpdate(changedProperties) { + if(changedProperties.has('isLoadingMessages')){ + return true + } // Only update element if prop1 changed. return changedProperties.has('messages'); } @@ -144,6 +154,7 @@ class ChatScroller extends LitElement { _upObserverhandler(entries) { if (entries[0].isIntersecting) { + this.setIsLoadingMessages(true) let _scrollElement = entries[0].target.nextElementSibling this._getOldMessage(_scrollElement) } diff --git a/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js b/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js index 7d984763..65a5ee59 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js +++ b/qortal-ui-plugins/plugins/core/components/ChatTextEditor.js @@ -250,7 +250,6 @@ class ChatTextEditor extends LitElement { } async firstUpdated() { - console.log(this.placeholder, "here500"); if (this.hasGlobalEvents) { this.addGlobalEventListener(); } @@ -301,7 +300,6 @@ class ChatTextEditor extends LitElement { this.chatEditor.insertText(this.editedMessageObj.message) } if (changedProperties && changedProperties.has('placeholder')) { - console.log(this.placeholder, "here600"); const captionEditor = this.shadowRoot.getElementById(this.iframeId).contentWindow.document.getElementById('testingId'); captionEditor.setAttribute('data-placeholder', this.placeholder); } @@ -371,8 +369,7 @@ class ChatTextEditor extends LitElement { } initChatEditor() { - const ChatEditor = function (editorConfig) { - console.log(editorConfig.placeholder, "here5600"); + const ChatEditor = function (editorConfig) { const ChatEditor = function () { const editor = this; editor.init(); From a4af679bdc818ca2bef3665dd1542c94e8ba2ff9 Mon Sep 17 00:00:00 2001 From: Phillip Lang Martinez Date: Wed, 30 Nov 2022 18:29:36 +0200 Subject: [PATCH 051/123] change image qdn service and max img size --- qortal-ui-core/language/us.json | 2 +- qortal-ui-plugins/plugins/core/components/ChatPage.js | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/qortal-ui-core/language/us.json b/qortal-ui-core/language/us.json index 4f414575..2aa00796 100644 --- a/qortal-ui-core/language/us.json +++ b/qortal-ui-core/language/us.json @@ -485,7 +485,7 @@ "cchange23": "Cannot Decrypt Message!", "cchange24": "Maximum Characters per message is 255", "cchange25": "Edit Message", - "cchange26": "File size exceeds 5 MB", + "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", diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index e87a9f67..51b00daa 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -1397,6 +1397,7 @@ class ChatPage extends LitElement { const file = new File([result], "name", { type: 'image/png' }); + compressedFile = file; resolve(); }, @@ -1409,7 +1410,7 @@ class ChatPage extends LitElement { await publishData({ registeredName: userName, file : compressedFile, - service: 'IMAGE', + service: 'QCHAT_IMAGE', identifier: identifier, parentEpml, metaData: undefined, @@ -1480,7 +1481,7 @@ class ChatPage extends LitElement { }) }) const fileSize = compressedFile.size; - if (fileSize > 5000000) { + if (fileSize > 500000) { parentEpml.request('showSnackBar', get("chatpage.cchange26")); this.isLoading = false; this.chatEditor.enable(); @@ -1492,7 +1493,7 @@ class ChatPage extends LitElement { await publishData({ registeredName: userName, file : compressedFile, - service: 'IMAGE', + service: 'QCHAT_IMAGE', identifier : identifier, parentEpml, metaData: undefined, @@ -1516,7 +1517,7 @@ class ChatPage extends LitElement { const messageObject = { messageText: trimmedMessageWithImage, images: [{ - service: "IMAGE", + service: "QCHAT_IMAGE", name: userName, identifier: identifier }], From 7d6568ce79da40b87d9cd51e57a21405fbb172c9 Mon Sep 17 00:00:00 2001 From: Justin Ferrari <‘justinwesleyferrari@gmail.com’> Date: Wed, 30 Nov 2022 14:17:29 -0500 Subject: [PATCH 052/123] Fixed the chat message size bug --- .../plugins/core/components/ChatPage.js | 33 +++---- .../plugins/core/components/ChatScroller.js | 27 +++--- .../plugins/core/components/ChatTextEditor.js | 89 ++++++++++++------- 3 files changed, 78 insertions(+), 71 deletions(-) diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js index 5efa10a6..ecca8d3d 100644 --- a/qortal-ui-plugins/plugins/core/components/ChatPage.js +++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js @@ -59,7 +59,7 @@ class ChatPage extends LitElement { repliedToMessageObj: { type: Object }, editedMessageObj: { type: Object }, iframeHeight: { type: Number }, - chatMessageSize: { type: Number}, + // chatMessageSize: { type: Number}, imageFile: { type: Object }, isUploadingImage: { type: Boolean }, chatEditor: { type: Object }, @@ -545,7 +545,7 @@ class ChatPage extends LitElement { this.repliedToMessageObj = null this.editedMessageObj = null this.iframeHeight = 42 - this.chatMessageSize = 0 + // this.chatMessageSize = 0 this.imageFile = null this.uid = new ShortUniqueId() this.userLanguage = "" @@ -619,7 +619,7 @@ class ChatPage extends LitElement {
`} -
+
- ${this.chatMessageSize >= 750 ? - html` -
-
- ${`Your message size is of ${this.chatMessageSize} bytes out of a maximum of 1000`} -
-
- ` : - html``}