From 9bd352577b63408a34b978c80f262b3314cc7751 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Wed, 4 Jun 2025 08:36:44 +0200 Subject: [PATCH 01/23] Some improvements --- src/i18n/locales/it/auth.json | 2 +- src/i18n/locales/it/core.json | 4 ++-- src/i18n/locales/it/group.json | 14 +++++++------- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/i18n/locales/it/auth.json b/src/i18n/locales/it/auth.json index df1139f..2d20e5c 100644 --- a/src/i18n/locales/it/auth.json +++ b/src/i18n/locales/it/auth.json @@ -117,7 +117,7 @@ "digital_id": "il tuo wallet è come il tuo ID digitale su Qortal ed e verrà usato per accedere a Qortal. Contiene il tuo indirizzo pubblico e il nome Qortal che alla fine sceglierai. Ogni transazione che fai è collegata al tuo ID, nel quale potrai gestire tutte le tue criptovalute Qort e altre criptovalute negoziabili su Qortal.", "existing_account": "hai già un account Qortal? Inserisci qui la tua frase di backup segreta per accedervi. Questa frase è uno dei modi per recuperare il tuo account.", "key_encrypt_admin": "questa chiave crittografa i contenuti relativi ad amministratore. Solo gli amministratori vedranno il contenuto crittografato.", - "key_encrypt_group": "questa chiave crittografa i contenuti relativi al gruppo. Questo al momento è l'unico modo usato. Tutti i membri del gruppo saranno in grado di vedere i contenuti crittografati con questa chiave.", + "key_encrypt_group": "questa chiave crittografa i contenuti relativi al gruppo. Per ora questo è l'unico modo usato. Tutti i membri del gruppo saranno in grado di vedere i contenuti crittografati con questa chiave.", "new_account": "la creazione di un account consiste nella creazione di un wallet e di un ID digitale per iniziare a utilizzare Qortal. Una volta creato l'account, potrai iniziare a fare cose come ottenere dei Qort, acquistare un nome e un Avatar, pubblicare video e blog e molto altro.", "new_users": "i nuovi utenti iniziano qui!", "safe_place": "salva il tuo account in un posto da ricordare!", diff --git a/src/i18n/locales/it/core.json b/src/i18n/locales/it/core.json index 59243c5..9484f7c 100644 --- a/src/i18n/locales/it/core.json +++ b/src/i18n/locales/it/core.json @@ -252,9 +252,9 @@ "no_minting_details": "impossibile visualizzare i dettagli di minire sul gateway", "no_notifications": "nessuna nuova notifica", "no_payments": "nessun pagamento", - "no_pinned_changes": "attualmente non hai modifiche alle tue app bloccate", + "no_pinned_changes": "per ora non ci sono modifiche alle app bloccate", "no_results": "nessun risultato", - "one_app_per_name": "nota: attualmente, sono consentiti solo un'app e un sito Web per nome.", + "one_app_per_name": "nota: per adesso sono consentiti solo un'app e un sito Web per nome.", "opened": "aperto", "overwrite_qdn": "sovrascrivi a QDN", "password_confirm": "si prega di confermare una password", diff --git a/src/i18n/locales/it/group.json b/src/i18n/locales/it/group.json index 12f1d5e..7357c94 100644 --- a/src/i18n/locales/it/group.json +++ b/src/i18n/locales/it/group.json @@ -77,28 +77,28 @@ "group_encrypted": "gruppo crittografato", "group_invited_you": "{{group}} ti ha invitato", "group_key_created": "creata la prima chiave di gruppo.", - "group_member_list_changed": "l'elenco dei membri del gruppo è cambiato. Si prega di ricriptare nuovamente la chiave segreta.", + "group_member_list_changed": "l'elenco dei membri del gruppo è cambiato. Si prega di recriptare nuovamente la chiave segreta.", "group_no_secret_key": "non esiste una chiave segreta di gruppo. Potresti essere il primo amministratore a pubblicarne una!", - "group_secret_key_no_owner": "l'ultima chiave segreta del gruppo è stata pubblicata da un non proprietario. Per sicurezza come proprietario del gruppo si prega di ricriptare la chiave.", + "group_secret_key_no_owner": "l'ultima chiave segreta del gruppo è stata pubblicata da un non proprietario. Per sicurezza, come proprietario del gruppo, si prega di recriptare la chiave.", "invalid_content": "contenuto non valido, mittente o timestamp nei dati di reazione", "invalid_data": "errore di caricamento del contenuto: dati non validi", "latest_promotion": "verrà mostrata solo l'ultima promozione della settimana per il tuo gruppo.", "loading_members": "caricamento dell'elenco dei membri con nomi ... Attendere.", "max_chars": "max 200 caratteri. Commissione", "manage_minting": "gestisci il minting", - "minter_group": "al momento non fai parte del gruppo Minter", + "minter_group": "per ora non fai parte del gruppo Minter", "mintership_app": "visita l'app Q-Mintership per chiedere di diventare un minter", "minting_account": "account di minting:", - "minting_keys_per_node": "sono ammessi solo 2 chiavi di minting per nodo. Rimuoverne una se si desidera fare minting con questo account.", - "minting_keys_per_node_different": "sono ammessi solo 2 chiavi di minting per nodo. Rimuovi uno se desideri aggiungere un account diverso.", + "minting_keys_per_node": "sono ammesse solo 2 chiavi di minting per nodo. Rimuoverne una se si desidera fare minting con questo account.", + "minting_keys_per_node_different": "sono ammesse solo 2 chiavi di minting per nodo. Rimuovine una se desideri aggiungere un account diverso.", "next_level": "blocchi mancanti al livello successivo:", "node_minting": "questo nodo sta coniando:", "node_minting_account": "account minting del nodo", - "node_minting_key": "attualmente hai una chiave di minting per questo account collegata al nodo", + "node_minting_key": "hai una chiave di minting per questo account collegata al nodo", "no_announcement": "nessun annuncio", "no_display": "niente da visualizzare", "no_selection": "nessun gruppo selezionato", - "not_part_group": "non fai parte del gruppo crittografato di membri. Attendere che un amministratore ricripti le chiavi.", + "not_part_group": "non fai parte del gruppo crittografato di membri. Attendere che un amministratore recripti le chiavi.", "only_encrypted": "verranno visualizzati solo messaggi non crittografati.", "only_private_groups": "verranno mostrati solo gruppi privati", "pending_join_requests": "{{ group }} ha {{ count }} richieste pendenti di adesione", From 957cbe9ef9d742f6d828df53a746e6fe8d94a005 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Wed, 4 Jun 2025 09:13:16 +0200 Subject: [PATCH 02/23] Refactor translation --- src/i18n/locales/es/auth.json | 2 +- src/i18n/locales/fr/auth.json | 2 +- src/i18n/locales/it/auth.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/i18n/locales/es/auth.json b/src/i18n/locales/es/auth.json index 5105809..d426e35 100644 --- a/src/i18n/locales/es/auth.json +++ b/src/i18n/locales/es/auth.json @@ -117,7 +117,7 @@ "digital_id": "su billetera es como su ID digital en Qortal, y es cómo iniciará sesión en la interfaz de usuario de Qortal. Sostiene su dirección pública y el nombre Qortal que eventualmente elegirá. Cada transacción que realice está vinculada a su ID, y aquí es donde administra todos sus Qort y otras criptomonedas comercializables en Qortal.", "existing_account": "¿Ya tienes una cuenta Qortal? Ingrese su frase de copia de seguridad secreta aquí para acceder a ella. Esta frase es una de las formas de recuperar su cuenta.", "key_encrypt_admin": "esta clave es cifrar el contenido relacionado con el administrador. Solo los administradores verían contenido encriptado con él.", - "key_encrypt_group": "esta clave es cifrar contenido relacionado con el grupo. Este es el único utilizado en esta interfaz de usuario a partir de ahora. Todos los miembros del grupo podrán ver contenido encriptado con esta clave.", + "key_encrypt_group": "esta clave es cifrar contenido relacionado con el grupo. Esta es el única utilizada en esta interfaz de usuario a partir de ahora. Todos los miembros del grupo podrán ver contenido encriptado con esta clave.", "new_account": "crear una cuenta significa crear una nueva billetera e ID digital para comenzar a usar Qortal. Una vez que haya hecho su cuenta, puede comenzar a hacer cosas como obtener algo de Qort, comprar un nombre y avatar, publicar videos y blogs, y mucho más.", "new_users": "¡Los nuevos usuarios comienzan aquí!", "safe_place": "¡Guarde su cuenta en un lugar donde la recordará!", diff --git a/src/i18n/locales/fr/auth.json b/src/i18n/locales/fr/auth.json index 1b91356..11b0e85 100644 --- a/src/i18n/locales/fr/auth.json +++ b/src/i18n/locales/fr/auth.json @@ -117,7 +117,7 @@ "digital_id": "votre portefeuille est comme votre ID numérique sur Qortal, et c'est comment vous vous connectez à l'interface utilisateur Qortal. Il détient votre adresse publique et le nom Qortal que vous allez éventuellement choisir. Chaque transaction que vous effectuez est liée à votre identifiant, et c'est là que vous gérez tous vos Qort et autres crypto-monnaies négociables sur Qortal.", "existing_account": "vous avez déjà un compte Qortal? Entrez ici votre phrase de sauvegarde secrète pour y accéder. Cette phrase est l'une des façons de récupérer votre compte.", "key_encrypt_admin": "cette clé est de crypter le contenu lié à l'administrateur. Seuls les administrateurs verraient du contenu chiffré avec.", - "key_encrypt_group": "cette clé est de chiffrer le contenu lié au groupe. C'est le seul utilisé dans cette interface utilisateur pour l'instant. Tous les membres du groupe pourront voir du contenu crypté avec cette clé.", + "key_encrypt_group": "cette clé est de chiffrer le contenu lié au groupe. C'est la seule utilisée dans cette interface utilisateur pour l'instant. Tous les membres du groupe pourront voir du contenu crypté avec cette clé.", "new_account": "la création d'un compte signifie créer un nouvel portefeuille et un nouvel ID numérique pour commencer à utiliser Qortal. Une fois que vous avez créé votre compte, vous pouvez commencer à faire des choses comme obtenir du Qort, acheter un nom et un avatar, publier des vidéos et des blogs, et bien plus encore.", "new_users": "les nouveaux utilisateurs commencent ici!", "safe_place": "enregistrez votre compte dans un endroit où vous vous en souviendrez!", diff --git a/src/i18n/locales/it/auth.json b/src/i18n/locales/it/auth.json index 2d20e5c..6c464e5 100644 --- a/src/i18n/locales/it/auth.json +++ b/src/i18n/locales/it/auth.json @@ -117,7 +117,7 @@ "digital_id": "il tuo wallet è come il tuo ID digitale su Qortal ed e verrà usato per accedere a Qortal. Contiene il tuo indirizzo pubblico e il nome Qortal che alla fine sceglierai. Ogni transazione che fai è collegata al tuo ID, nel quale potrai gestire tutte le tue criptovalute Qort e altre criptovalute negoziabili su Qortal.", "existing_account": "hai già un account Qortal? Inserisci qui la tua frase di backup segreta per accedervi. Questa frase è uno dei modi per recuperare il tuo account.", "key_encrypt_admin": "questa chiave crittografa i contenuti relativi ad amministratore. Solo gli amministratori vedranno il contenuto crittografato.", - "key_encrypt_group": "questa chiave crittografa i contenuti relativi al gruppo. Per ora questo è l'unico modo usato. Tutti i membri del gruppo saranno in grado di vedere i contenuti crittografati con questa chiave.", + "key_encrypt_group": "questa chiave crittografa i contenuti relativi al gruppo. Per ora questa è l'unica utilizzata in quest'interfaccia grafica. Tutti i membri del gruppo saranno in grado di vedere i contenuti crittografati con questa chiave.", "new_account": "la creazione di un account consiste nella creazione di un wallet e di un ID digitale per iniziare a utilizzare Qortal. Una volta creato l'account, potrai iniziare a fare cose come ottenere dei Qort, acquistare un nome e un Avatar, pubblicare video e blog e molto altro.", "new_users": "i nuovi utenti iniziano qui!", "safe_place": "salva il tuo account in un posto da ricordare!", From 4ddd2cd589ef115f003c7003026b51ee27d57c1b Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Wed, 4 Jun 2025 09:15:46 +0200 Subject: [PATCH 03/23] Update --- src/i18n/locales/it/auth.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/i18n/locales/it/auth.json b/src/i18n/locales/it/auth.json index 6c464e5..18055c7 100644 --- a/src/i18n/locales/it/auth.json +++ b/src/i18n/locales/it/auth.json @@ -95,7 +95,7 @@ "your_accounts": "i tuoi conti salvati" }, "success": { - "reencrypted_secret_key": "chiave segreta recriptata con successo. Potrebbero essere necessari un paio di minuti per propagare le modifiche. Aggiorna il gruppo in 5 minuti." + "reencrypted_secret_key": "chiave segreta recriptata con successo. Potrebbero essere necessari un paio di minuti per propagare le modifiche. Aggiorna il gruppo fra 5 minuti." } }, "node": { @@ -122,7 +122,7 @@ "new_users": "i nuovi utenti iniziano qui!", "safe_place": "salva il tuo account in un posto da ricordare!", "view_seedphrase": "se si desidera visualizzare la seed phrase, fai clic sulla parola \"seed phrase\" in questo testo. Le seed phrase vengono utilizzate per generare la chiave privata per il tuo account Qortal. Per la sicurezza per impostazione predefinita, le seed phrase non vengono visualizzate se non specificamente scelte.", - "wallet_secure": "mantieni al sicuro il tuo file di wallet." + "wallet_secure": "mantieni al sicuro il tuo file del wallet." }, "wallet": { "password_confirmation": "conferma la password del wallet", From e0eb53530e0dc26035e7be5faf8ef2ec1d2d5b50 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Wed, 4 Jun 2025 14:33:58 +0300 Subject: [PATCH 04/23] change support text and link --- src/components/NewUsersCTA.tsx | 9 ++--- src/constants/constants.ts | 3 ++ src/i18n/locales/de/core.json | 2 +- src/i18n/locales/de/question.json | 4 ++- src/i18n/locales/en/core.json | 2 +- src/i18n/locales/en/question.json | 4 ++- src/i18n/locales/es/core.json | 2 +- src/i18n/locales/es/question.json | 4 ++- src/i18n/locales/fr/core.json | 2 +- src/i18n/locales/fr/question.json | 4 ++- src/i18n/locales/it/core.json | 2 +- src/i18n/locales/it/question.json | 4 ++- src/i18n/locales/ja/core.json | 2 +- src/i18n/locales/ja/question.json | 4 ++- src/i18n/locales/ru/core.json | 2 +- src/i18n/locales/ru/question.json | 4 ++- src/i18n/locales/zh/core.json | 2 +- src/i18n/locales/zh/question.json | 4 ++- src/qortal/get.ts | 57 ++++++++++++++++++++++++++++++- 19 files changed, 94 insertions(+), 23 deletions(-) diff --git a/src/components/NewUsersCTA.tsx b/src/components/NewUsersCTA.tsx index d444717..0618ce5 100644 --- a/src/components/NewUsersCTA.tsx +++ b/src/components/NewUsersCTA.tsx @@ -70,17 +70,14 @@ export const NewUsersCTA = ({ balance }) => { onClick={() => { if (window?.electronAPI?.openExternal) { window.electronAPI.openExternal( - 'https://link.qortal.dev/telegram-invite' + 'https://link.qortal.dev/support' ); } else { - window.open( - 'https://link.qortal.dev/telegram-invite', - '_blank' - ); + window.open('https://link.qortal.dev/support', '_blank'); } }} > - Telegram + Nextcloud MAX_SIZE_PUBLISH) { + throw new Error( + i18n.t('question:message.error.max_size_publish', { + size: 2, + postProcess: 'capitalizeFirstChar', + }) + ); + } + + if (file && file.size > MAX_SIZE_PUBLIC_NODE) { + const isPublicNode = await isRunningGateway(); + if (isPublicNode) { + throw new Error( + i18n.t('question:message.error.max_size_publish_public', { + size: 500, + postProcess: 'capitalizeFirstChar', + }) + ); + } + } + // Fill tags dynamically while maintaining backward compatibility for (let i = 0; i < 5; i++) { result[`tag${i + 1}`] = tags[i] || data[`tag${i + 1}`] || undefined; @@ -1542,6 +1567,36 @@ export const publishMultipleQDNResources = async ( }) ); } + const isPublicNode = await isRunningGateway(); + if (isPublicNode) { + const hasOversizedFilePublicNode = resources.some((resource) => { + const file = resource?.file; + return file instanceof File && file.size > MAX_SIZE_PUBLIC_NODE; + }); + + if (hasOversizedFilePublicNode) { + throw new Error( + i18n.t('question:message.error.max_size_publish_public', { + size: 500, + postProcess: 'capitalizeFirstChar', + }) + ); + } + } + + const hasOversizedFile = resources.some((resource) => { + const file = resource?.file; + return file instanceof File && file.size > MAX_SIZE_PUBLISH; + }); + + if (hasOversizedFile) { + throw new Error( + i18n.t('question:message.error.max_size_publish', { + size: 2, + postProcess: 'capitalizeFirstChar', + }) + ); + } const encrypt = data?.encrypt; From 170de54eb674a30b5e0a4b5c2e4a7b54d53edac8 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Wed, 4 Jun 2025 19:28:40 +0200 Subject: [PATCH 05/23] Use const instead of let, remove unused vars --- src/qdn/encryption/group-encryption.ts | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/qdn/encryption/group-encryption.ts b/src/qdn/encryption/group-encryption.ts index 6f018ba..ebead90 100644 --- a/src/qdn/encryption/group-encryption.ts +++ b/src/qdn/encryption/group-encryption.ts @@ -77,7 +77,7 @@ export const encryptDataGroup = ({ userPublicKey, customSymmetricKey, }: any) => { - let combinedPublicKeys = [...publicKeys, userPublicKey]; + const combinedPublicKeys = [...publicKeys, userPublicKey]; const decodedPrivateKey = Base58.decode(privateKey); const publicKeysDuplicateFree = [...new Set(combinedPublicKeys)]; const Uint8ArrayData = base64ToUint8Array(data64); @@ -114,7 +114,7 @@ export const encryptDataGroup = ({ const keyNonce = new Uint8Array(24); crypto.getRandomValues(keyNonce); // Encrypt the symmetric key for each recipient. - let encryptedKeys = []; + const encryptedKeys = []; publicKeysDuplicateFree.forEach((recipientPublicKey) => { const publicKeyUnit8Array = Base58.decode(recipientPublicKey); const convertedPrivateKey = ed2curve.convertSecretKey(decodedPrivateKey); @@ -153,7 +153,7 @@ export const encryptDataGroup = ({ encryptedKeysSize += key.length; }); combinedDataSize += encryptedKeysSize; - let combinedData = new Uint8Array(combinedDataSize); + const combinedData = new Uint8Array(combinedDataSize); combinedData.set(strUint8Array); combinedData.set(nonce, strUint8Array.length); combinedData.set(keyNonce, strUint8Array.length + nonce.length); @@ -244,9 +244,6 @@ export const encryptSingle = async ({ encryptedData = nacl.secretbox(Uint8ArrayData, nonce, messageKey); encryptedDataBase64 = uint8ArrayToBase64(encryptedData); - // Convert the nonce to base64 - const nonceBase64 = uint8ArrayToBase64(nonce); - // Concatenate the highest key, type number, nonce, and encrypted data (new format) const highestKeyStr = highestKey.toString().padStart(10, '0'); // Fixed length of 10 digits @@ -281,7 +278,7 @@ export const encryptSingle = async ({ }; export const decodeBase64ForUIChatMessages = (messages) => { - let msgs = []; + const msgs = []; for (const msg of messages) { try { const decoded = atob(msg?.data); @@ -326,7 +323,7 @@ export const decryptSingle = async ({ const secretKeyEntry = secretKeyObject[highestKey]; - let typeNumberStr, nonceBase64, encryptedDataBase64; + let nonceBase64, encryptedDataBase64; // Determine if typeNumber exists by checking if the next 3 characters after keyStr are digits const possibleTypeNumberStr = decodeForNumber.slice(10, 13); @@ -370,7 +367,6 @@ export const decryptSingle = async ({ return uint8ArrayToBase64(decryptedBytes); } // New format: Extract type number and nonce - typeNumberStr = possibleTypeNumberStr; // Extract type number nonceBase64 = decodeForNumber.slice(13, 45); // Extract nonce (next 32 characters after type number) encryptedDataBase64 = decodeForNumber.slice(45); // The remaining part is the encrypted data } else { @@ -424,14 +420,9 @@ export const decryptGroupEncryptionWithSharingKey = async ({ // Extract the shared keyNonce const keyNonceStartPosition = nonceEndPosition; const keyNonceEndPosition = keyNonceStartPosition + 24; // Nonce is 24 bytes - const keyNonce = allCombined.slice( - keyNonceStartPosition, - keyNonceEndPosition - ); // Extract the sender's public key const senderPublicKeyStartPosition = keyNonceEndPosition; const senderPublicKeyEndPosition = senderPublicKeyStartPosition + 32; // Public keys are 32 bytes - // Calculate count first const countStartPosition = allCombined.length - 4; // 4 bytes before the end, since count is stored in Uint32 (4 bytes) const countArray = allCombined.slice( @@ -662,7 +653,6 @@ export function decryptDeprecatedSingle(uint8Array, publicKey, privateKey) { const str = 'qortalEncryptedData'; const strEncoder = new TextEncoder(); const strUint8Array = strEncoder.encode(str); - const strData = combinedData.slice(0, strUint8Array.length); const nonce = combinedData.slice( strUint8Array.length, strUint8Array.length + 24 From 7f7f76d40e899d9f3c2bba793b5555696cc74ff5 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Wed, 4 Jun 2025 19:35:52 +0200 Subject: [PATCH 06/23] Set default src as null --- src/components/Embeds/ImageEmbed.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/Embeds/ImageEmbed.tsx b/src/components/Embeds/ImageEmbed.tsx index ffa288e..5440862 100644 --- a/src/components/Embeds/ImageEmbed.tsx +++ b/src/components/Embeds/ImageEmbed.tsx @@ -197,7 +197,7 @@ export const ImageCard = ({ ); }; -export function ImageViewer({ src, alt = '' }) { +export function ImageViewer({ src = null, alt = '' }) { const [isFullscreen, setIsFullscreen] = useState(false); const handleOpenFullscreen = () => setIsFullscreen(true); From 298aa35425e8017696eb476204254513d3845cf1 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Thu, 5 Jun 2025 20:10:29 +0200 Subject: [PATCH 07/23] Add optional --- src/qortal/qortal-requests.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/qortal/qortal-requests.ts b/src/qortal/qortal-requests.ts index f67b922..ba04c96 100644 --- a/src/qortal/qortal-requests.ts +++ b/src/qortal/qortal-requests.ts @@ -152,7 +152,7 @@ function setupMessageListenerQortalRequest() { appInfo, skipAuth, }); - event.source.postMessage( + event.source!.postMessage( { requestId: request.requestId, action: request.action, @@ -162,7 +162,7 @@ function setupMessageListenerQortalRequest() { event.origin ); } catch (error) { - event.source.postMessage( + event.source!.postMessage( { requestId: request.requestId, action: request.action, @@ -236,7 +236,7 @@ function setupMessageListenerQortalRequest() { request.payload, event.source ); - event.source.postMessage( + event.source!.postMessage( { requestId: request.requestId, action: request.action, @@ -246,7 +246,7 @@ function setupMessageListenerQortalRequest() { event.origin ); } catch (error) { - event.source.postMessage( + event.source!.postMessage( { requestId: request.requestId, action: request.action, From c053122d634aaf89eaacdd5e29369354225d9441 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Thu, 5 Jun 2025 20:11:53 +0200 Subject: [PATCH 08/23] Use const instead of let, add comment --- src/qortal/get.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/qortal/get.ts b/src/qortal/get.ts index 368bd50..1af583f 100644 --- a/src/qortal/get.ts +++ b/src/qortal/get.ts @@ -552,7 +552,7 @@ export const getUserAccount = async ({ export const encryptData = async (data, sender) => { let data64 = data.data64 || data.base64; - let publicKeys = data.publicKeys || []; + const publicKeys = data.publicKeys || []; if (data?.file || data?.blob) { data64 = await fileToBase64(data?.file || data?.blob); } @@ -587,8 +587,8 @@ export const encryptData = async (data, sender) => { export const encryptQortalGroupData = async (data, sender) => { let data64 = data?.data64 || data?.base64; - let groupId = data?.groupId; - let isAdmins = data?.isAdmins; + const groupId = data?.groupId; + const isAdmins = data?.isAdmins; if (!groupId) { throw new Error( i18n.t('question:message.generic.provide_group_id', { @@ -613,7 +613,7 @@ export const encryptQortalGroupData = async (data, sender) => { groupSecretkeys[groupId] && groupSecretkeys[groupId].secretKeyObject && groupSecretkeys[groupId]?.timestamp && - Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 + Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 // TODO magic number ) { secretKeyObject = groupSecretkeys[groupId].secretKeyObject; } @@ -659,7 +659,7 @@ export const encryptQortalGroupData = async (data, sender) => { groupSecretkeys[`admins-${groupId}`] && groupSecretkeys[`admins-${groupId}`].secretKeyObject && groupSecretkeys[`admins-${groupId}`]?.timestamp && - Date.now() - groupSecretkeys[`admins-${groupId}`]?.timestamp < 1200000 + Date.now() - groupSecretkeys[`admins-${groupId}`]?.timestamp < 1200000 // TODO magic number ) { secretKeyObject = groupSecretkeys[`admins-${groupId}`].secretKeyObject; } @@ -717,9 +717,9 @@ export const encryptQortalGroupData = async (data, sender) => { }; export const decryptQortalGroupData = async (data, sender) => { - let data64 = data?.data64 || data?.base64; - let groupId = data?.groupId; - let isAdmins = data?.isAdmins; + const data64 = data?.data64 || data?.base64; + const groupId = data?.groupId; + const isAdmins = data?.isAdmins; if (!groupId) { throw new Error( i18n.t('question:message.generic.provide_group_id', { @@ -742,7 +742,7 @@ export const decryptQortalGroupData = async (data, sender) => { groupSecretkeys[groupId] && groupSecretkeys[groupId].secretKeyObject && groupSecretkeys[groupId]?.timestamp && - Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 + Date.now() - groupSecretkeys[groupId]?.timestamp < 1200000 // TODO magic number ) { secretKeyObject = groupSecretkeys[groupId].secretKeyObject; } @@ -785,7 +785,7 @@ export const decryptQortalGroupData = async (data, sender) => { groupSecretkeys[`admins-${groupId}`] && groupSecretkeys[`admins-${groupId}`].secretKeyObject && groupSecretkeys[`admins-${groupId}`]?.timestamp && - Date.now() - groupSecretkeys[`admins-${groupId}`]?.timestamp < 1200000 + Date.now() - groupSecretkeys[`admins-${groupId}`]?.timestamp < 1200000 // TODO magic nummber ) { secretKeyObject = groupSecretkeys[`admins-${groupId}`].secretKeyObject; } @@ -843,7 +843,7 @@ export const decryptQortalGroupData = async (data, sender) => { export const encryptDataWithSharingKey = async (data, sender) => { let data64 = data?.data64 || data?.base64; - let publicKeys = data.publicKeys || []; + const publicKeys = data.publicKeys || []; if (data?.file || data?.blob) { data64 = await fileToBase64(data?.file || data?.blob); } @@ -899,6 +899,7 @@ export const decryptDataWithSharingKey = async (data, sender) => { data64EncryptedData: encryptedData, key, }); + const base64ToObject = JSON.parse(atob(decryptedData)); if (!base64ToObject.data) From 1e50cf8cd80b380b0b5f24f6e7448fb6d8a9924c Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Thu, 5 Jun 2025 20:12:57 +0200 Subject: [PATCH 09/23] Sort parames --- src/components/Chat/MessageItem.tsx | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index b16b07f..65d6f80 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -100,22 +100,22 @@ const UserBadge = memo(({ userInfo }) => { export const MessageItem = memo( ({ - message, - onSeen, + handleReaction, isLast, - isTemp, - myAddress, - onReply, + isPrivate, isShowingAsReply, + isTemp, + isUpdating, + lastSignature, + message, + myAddress, + onEdit, + onReply, + onSeen, + reactions, reply, replyIndex, scrollToItem, - handleReaction, - reactions, - isUpdating, - lastSignature, - onEdit, - isPrivate, }) => { const { getIndividualUserInfo } = useContext(QORTAL_APP_CONTEXT); const [anchorEl, setAnchorEl] = useState(null); From 1e078e1324f20c6fbfffbb5c9d38dda278b3eb59 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Thu, 5 Jun 2025 20:48:35 +0200 Subject: [PATCH 10/23] Pass null instead of empty string --- src/components/Embeds/Embed.tsx | 2 +- src/components/Embeds/ImageEmbed.tsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/Embeds/Embed.tsx b/src/components/Embeds/Embed.tsx index 0038830..7aa2e39 100644 --- a/src/components/Embeds/Embed.tsx +++ b/src/components/Embeds/Embed.tsx @@ -59,7 +59,7 @@ export const Embed = ({ embedLink }) => { const [openSnack, setOpenSnack] = useState(false); const [infoSnack, setInfoSnack] = useState(null); const [external, setExternal] = useState(null); - const [imageUrl, setImageUrl] = useState(''); + const [imageUrl, setImageUrl] = useState(null); const [parsedData, setParsedData] = useState(null); const setBlobs = useSetAtom(blobControllerAtom); const [selectedGroupId] = useAtom(selectedGroupIdAtom); diff --git a/src/components/Embeds/ImageEmbed.tsx b/src/components/Embeds/ImageEmbed.tsx index 5440862..13ec3e2 100644 --- a/src/components/Embeds/ImageEmbed.tsx +++ b/src/components/Embeds/ImageEmbed.tsx @@ -199,7 +199,6 @@ export const ImageCard = ({ export function ImageViewer({ src = null, alt = '' }) { const [isFullscreen, setIsFullscreen] = useState(false); - const handleOpenFullscreen = () => setIsFullscreen(true); const handleCloseFullscreen = () => setIsFullscreen(false); const theme = useTheme(); From 5444bacdd0657b53a5e33d656113ebfd97229252 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Thu, 5 Jun 2025 21:42:33 +0200 Subject: [PATCH 11/23] Add check condition --- src/background/background.ts | 31 ++-- src/components/Group/Group.tsx | 18 +-- src/qdn/encryption/group-encryption.ts | 190 +++++++++++++------------ 3 files changed, 127 insertions(+), 112 deletions(-) diff --git a/src/background/background.ts b/src/background/background.ts index 2057914..fce3d83 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -440,7 +440,7 @@ const handleNotificationDirect = async (directs) => { let isFocused; const wallet = await getSaveWallet(); const address = wallet.address0; - let isDisableNotifications = + const isDisableNotifications = (await getUserSettings({ key: 'disable-push-notifications' })) || false; const dataDirects = directs.filter((direct) => direct?.sender !== address); try { @@ -1734,7 +1734,7 @@ export async function decryptSingleFunc({ secretKeyObject, skipDecodeBase64, }) { - let holdMessages = []; + const holdMessages = []; for (const message of messages) { try { @@ -1744,9 +1744,11 @@ export async function decryptSingleFunc({ skipDecodeBase64, }); - const decryptToUnit8Array = base64ToUint8Array(res); - const responseData = uint8ArrayToObject(decryptToUnit8Array); - holdMessages.push({ ...message, decryptedData: responseData }); + if (res) { + const decryptToUnit8Array = base64ToUint8Array(res); + const responseData = uint8ArrayToObject(decryptToUnit8Array); + holdMessages.push({ ...message, decryptedData: responseData }); + } } catch (error) { console.error(error); } @@ -1758,7 +1760,7 @@ export async function decryptSingleForPublishes({ secretKeyObject, skipDecodeBase64, }) { - let holdMessages = []; + const holdMessages = []; for (const message of messages) { try { @@ -2888,6 +2890,7 @@ export async function getTimestampEnterChat() { return {}; } } + export async function getTimestampMention() { const wallet = await getSaveWallet(); const address = wallet.address0; @@ -2900,6 +2903,7 @@ export async function getTimestampMention() { return {}; } } + export async function getTimestampGroupAnnouncement() { const wallet = await getSaveWallet(); const address = wallet.address0; @@ -2996,6 +3000,7 @@ async function getGroupData() { return {}; } } + export async function getGroupDataSingle(groupId) { const wallet = await getSaveWallet(); const address = wallet.address0; @@ -3266,6 +3271,7 @@ function setupMessageListener() { break; case 'updateThreadActivity': updateThreadActivityCase(request, event); + break; case 'decryptGroupEncryption': decryptGroupEncryptionCase(request, event); break; @@ -3411,7 +3417,7 @@ export const checkNewMessages = async () => { myName = userData.name; } - let newAnnouncements = []; + const newAnnouncements = []; const activeData = (await getStoredData('active-groups-directs')) || { groups: [], directs: [], @@ -3463,7 +3469,8 @@ export const checkNewMessages = async () => { } }) ); - let isDisableNotifications = + + const isDisableNotifications = (await getUserSettings({ key: 'disable-push-notifications' })) || false; if ( @@ -3611,8 +3618,8 @@ export const checkThreads = async (bringBack) => { if (userData?.name) { myName = userData.name; } - let newAnnouncements = []; - let dataToBringBack = []; + const newAnnouncements = []; + const dataToBringBack = []; const threadActivity = await getThreadActivity(); if (!threadActivity) return null; @@ -3627,7 +3634,6 @@ export const checkThreads = async (bringBack) => { for (const thread of selectedThreads) { try { const identifier = `thmsg-${thread?.threadId}`; - const name = thread?.qortalName; const endpoint = await getArbitraryEndpoint(); const url = await createEndpoint( `${endpoint}?mode=ALL&service=DOCUMENT&identifier=${identifier}&limit=1&includemetadata=false&offset=${0}&reverse=true&prefix=true` @@ -3643,7 +3649,6 @@ export const checkThreads = async (bringBack) => { const latestMessage = responseData.filter( (pub) => pub?.name !== myName )[0]; - // const latestMessage = responseData[0] if (!latestMessage) { continue; @@ -3717,7 +3722,7 @@ export const checkThreads = async (bringBack) => { '_type=thread-post' + `_data=${JSON.stringify(newAnnouncements[0])}` ); - let isDisableNotifications = + const isDisableNotifications = (await getUserSettings({ key: 'disable-push-notifications' })) || false; if (!isDisableNotifications) { // Check user settings to see if notifications are disabled diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index f4091f2..dc8209d 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -276,15 +276,13 @@ export async function getNameInfo(address: string) { } export const getGroupAdmins = async (groupNumber: number) => { - // const validApi = await findUsableApi(); - const response = await fetch( `${getBaseApiReact()}/groups/members/${groupNumber}?limit=0&onlyAdmins=true` ); const groupData = await response.json(); - let members: any = []; - let membersAddresses = []; - let both = []; + const members: any = []; + const membersAddresses = []; + const both = []; const getMemNames = groupData?.members?.map(async (member) => { if (member?.member) { @@ -601,9 +599,11 @@ export const Group = ({ const getGroupOwner = async (groupId) => { try { + if (groupId == 0) return; + const url = `${getBaseApiReact()}/groups/${groupId}`; const response = await fetch(url); - let data = await response.json(); + const data = await response.json(); const name = await getNameInfo(data?.owner); if (name) { @@ -742,7 +742,7 @@ export const Group = ({ data = await res.text(); } - const decryptedKey: any = await decryptResource(data); + const decryptedKey: any = await decryptResource(data, null); const dataint8Array = base64ToUint8Array(decryptedKey.data); const decryptedKeyToObject = uint8ArrayToObject(dataint8Array); @@ -877,6 +877,8 @@ export const Group = ({ }; const getOwnerNameForGroup = async (owner: string, groupId: string) => { + if (groupId == '0') return; + try { if (!owner) return; if (groupsOwnerNamesRef.current[groupId]) return; @@ -899,7 +901,7 @@ export const Group = ({ const url = `${getBaseApiReact()}/groups/member/${address}`; const response = await fetch(url); if (!response.ok) throw new Error('Cannot get group properties'); - let data = await response.json(); + const data = await response.json(); const transformToObject = data.reduce((result, item) => { result[item.groupId] = item; return result; diff --git a/src/qdn/encryption/group-encryption.ts b/src/qdn/encryption/group-encryption.ts index ebead90..904413c 100644 --- a/src/qdn/encryption/group-encryption.ts +++ b/src/qdn/encryption/group-encryption.ts @@ -303,106 +303,114 @@ export const decryptSingle = async ({ // First, decode the base64-encoded input (if skipDecodeBase64 is not set) const decodedData = skipDecodeBase64 ? data64 : atob(data64); - // Then, decode it again for the specific format (if double encoding is used) - const decodeForNumber = atob(decodedData); + if (secretKeyObject) { + // Then, decode it again for the specific format (if double encoding is used) + const decodeForNumber = atob(decodedData); - // Extract the key (assuming it's always the first 10 characters) - const keyStr = decodeForNumber.slice(0, 10); + // Extract the key (assuming it's always the first 10 characters) + const keyStr = decodeForNumber.slice(0, 10); - // Convert the key string back to a number - const highestKey = parseInt(keyStr, 10); + // Convert the key string back to a number + const highestKey = parseInt(keyStr, 10); - // Check if we have a valid secret key for the extracted highestKey - if (!secretKeyObject[highestKey]) { - throw new Error( - i18n.t('auth:message.error.find_secret_key', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - const secretKeyEntry = secretKeyObject[highestKey]; - - let nonceBase64, encryptedDataBase64; - - // Determine if typeNumber exists by checking if the next 3 characters after keyStr are digits - const possibleTypeNumberStr = decodeForNumber.slice(10, 13); - const hasTypeNumber = /^\d{3}$/.test(possibleTypeNumberStr); // Check if next 3 characters are digits - - if (secretKeyEntry.nonce) { - // Old format: nonce is present in the secretKeyObject, so no type number exists - nonceBase64 = secretKeyEntry.nonce; - encryptedDataBase64 = decodeForNumber.slice(10); // The remaining part is the encrypted data - } else { - if (hasTypeNumber) { - // const typeNumberStr = new TextDecoder().decode(typeNumberBytes); - if (decodeForNumber.slice(10, 13) !== '001') { - const decodedBinary = base64ToUint8Array(decodedData); - const highestKeyBytes = decodedBinary.slice(0, 10); // if ASCII digits only - const highestKeyStr = new TextDecoder().decode(highestKeyBytes); - - const nonce = decodedBinary.slice(13, 13 + 24); - const encryptedData = decodedBinary.slice(13 + 24); - const highestKey = parseInt(highestKeyStr, 10); - - const messageKey = base64ToUint8Array( - secretKeyObject[+highestKey].messageKey - ); - const decryptedBytes = nacl.secretbox.open( - encryptedData, - nonce, - messageKey - ); - - // Check if decryption was successful - if (!decryptedBytes) { - throw new Error( - i18n.t('question:message.error.decryption_failed', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - // Convert the decrypted Uint8Array back to a Base64 string - return uint8ArrayToBase64(decryptedBytes); - } - // New format: Extract type number and nonce - nonceBase64 = decodeForNumber.slice(13, 45); // Extract nonce (next 32 characters after type number) - encryptedDataBase64 = decodeForNumber.slice(45); // The remaining part is the encrypted data - } else { - // Old format without type number (nonce is embedded in the message, first 32 characters after keyStr) - nonceBase64 = decodeForNumber.slice(10, 42); // First 32 characters for the nonce - encryptedDataBase64 = decodeForNumber.slice(42); // The remaining part is the encrypted data + // Check if we have a valid secret key for the extracted highestKey + if (!secretKeyObject[highestKey]) { + throw new Error( + i18n.t('auth:message.error.find_secret_key', { + postProcess: 'capitalizeFirstChar', + }) + ); } - } - // Convert Base64 strings to Uint8Array - const Uint8ArrayData = base64ToUint8Array(encryptedDataBase64); - const nonce = base64ToUint8Array(nonceBase64); - const messageKey = base64ToUint8Array(secretKeyEntry.messageKey); + const secretKeyEntry = secretKeyObject[highestKey]; - if (!(Uint8ArrayData instanceof Uint8Array)) { - throw new Error( - i18n.t('auth:message.error.invalid_uint8', { - postProcess: 'capitalizeFirstChar', - }) + let nonceBase64, encryptedDataBase64; + + // Determine if typeNumber exists by checking if the next 3 characters after keyStr are digits + const possibleTypeNumberStr = decodeForNumber.slice(10, 13); + const hasTypeNumber = /^\d{3}$/.test(possibleTypeNumberStr); // Check if next 3 characters are digits + + if (secretKeyEntry.nonce) { + // Old format: nonce is present in the secretKeyObject, so no type number exists + nonceBase64 = secretKeyEntry.nonce; + encryptedDataBase64 = decodeForNumber.slice(10); // The remaining part is the encrypted data + } else { + if (hasTypeNumber) { + // const typeNumberStr = new TextDecoder().decode(typeNumberBytes); + if (decodeForNumber.slice(10, 13) !== '001') { + const decodedBinary = base64ToUint8Array(decodedData); + const highestKeyBytes = decodedBinary.slice(0, 10); // if ASCII digits only + const highestKeyStr = new TextDecoder().decode(highestKeyBytes); + + const nonce = decodedBinary.slice(13, 13 + 24); + const encryptedData = decodedBinary.slice(13 + 24); + const highestKey = parseInt(highestKeyStr, 10); + + const messageKey = base64ToUint8Array( + secretKeyObject[+highestKey].messageKey + ); + const decryptedBytes = nacl.secretbox.open( + encryptedData, + nonce, + messageKey + ); + + // Check if decryption was successful + if (!decryptedBytes) { + throw new Error( + i18n.t('question:message.error.decryption_failed', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + // Convert the decrypted Uint8Array back to a Base64 string + return uint8ArrayToBase64(decryptedBytes); + } + // New format: Extract type number and nonce + nonceBase64 = decodeForNumber.slice(13, 45); // Extract nonce (next 32 characters after type number) + encryptedDataBase64 = decodeForNumber.slice(45); // The remaining part is the encrypted data + } else { + // Old format without type number (nonce is embedded in the message, first 32 characters after keyStr) + nonceBase64 = decodeForNumber.slice(10, 42); // First 32 characters for the nonce + encryptedDataBase64 = decodeForNumber.slice(42); // The remaining part is the encrypted data + } + } + + // Convert Base64 strings to Uint8Array + const Uint8ArrayData = base64ToUint8Array(encryptedDataBase64); + const nonce = base64ToUint8Array(nonceBase64); + const messageKey = base64ToUint8Array(secretKeyEntry.messageKey); + + if (!(Uint8ArrayData instanceof Uint8Array)) { + throw new Error( + i18n.t('auth:message.error.invalid_uint8', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + // Decrypt the data using the nonce and messageKey + const decryptedData = nacl.secretbox.open( + Uint8ArrayData, + nonce, + messageKey ); + + // Check if decryption was successful + if (!decryptedData) { + throw new Error( + i18n.t('question:message.error.decryption_failed', { + postProcess: 'capitalizeFirstChar', + }) + ); + } + + // Convert the decrypted Uint8Array back to a Base64 string + return uint8ArrayToBase64(decryptedData); } - // Decrypt the data using the nonce and messageKey - const decryptedData = nacl.secretbox.open(Uint8ArrayData, nonce, messageKey); - - // Check if decryption was successful - if (!decryptedData) { - throw new Error( - i18n.t('question:message.error.decryption_failed', { - postProcess: 'capitalizeFirstChar', - }) - ); - } - - // Convert the decrypted Uint8Array back to a Base64 string - return uint8ArrayToBase64(decryptedData); + return; }; export const decryptGroupEncryptionWithSharingKey = async ({ From a3b5778da17be4630e30844aae1da687c8c0a188 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Fri, 6 Jun 2025 06:32:41 +0200 Subject: [PATCH 12/23] Add type --- src/components/Chat/MessageItem.tsx | 932 ++++++++++++++-------------- 1 file changed, 477 insertions(+), 455 deletions(-) diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index 65d6f80..c129408 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -98,533 +98,555 @@ const UserBadge = memo(({ userInfo }) => { ); }); -export const MessageItem = memo( - ({ - handleReaction, - isLast, - isPrivate, - isShowingAsReply, - isTemp, - isUpdating, - lastSignature, - message, - myAddress, - onEdit, - onReply, - onSeen, - reactions, - reply, - replyIndex, - scrollToItem, - }) => { - const { getIndividualUserInfo } = useContext(QORTAL_APP_CONTEXT); - const [anchorEl, setAnchorEl] = useState(null); - const [selectedReaction, setSelectedReaction] = useState(null); - const [userInfo, setUserInfo] = useState(null); +type MessageItemProps = { + handleReaction: (reaction: string, messageId: string) => void; + isLast: boolean; + isPrivate: boolean; + isShowingAsReply?: boolean; + isTemp: boolean; + isUpdating: boolean; + lastSignature: any; + message: any; + myAddress: any; + onEdit: (messageId: string) => void; + onReply: (messageId: string) => void; + onSeen: () => void; + reactions: any; // could be null, or type it more strictly + reply: any; // same here + replyIndex: number; + scrollToItem: (index: number) => void; +}; - useEffect(() => { - const getInfo = async () => { - if (!message?.sender) return; - try { - const res = await getIndividualUserInfo(message?.sender); - if (!res) return null; - setUserInfo(res); - } catch (error) { - // - } - }; +export const MessageItemComponent = ({ + handleReaction, + isLast, + isPrivate, + isShowingAsReply, + isTemp, + isUpdating, + lastSignature, + message, + myAddress, + onEdit, + onReply, + onSeen, + reactions, + reply, + replyIndex, + scrollToItem, +}: MessageItemProps) => { + const { getIndividualUserInfo } = useContext(QORTAL_APP_CONTEXT); + const [anchorEl, setAnchorEl] = useState(null); + const [selectedReaction, setSelectedReaction] = useState(null); + const [userInfo, setUserInfo] = useState(null); - getInfo(); - }, [message?.sender, getIndividualUserInfo]); - - const htmlText = useMemo(() => { - if (message?.messageText) { - const isHtml = isHtmlString(message?.messageText); - if (isHtml) return message?.messageText; - return generateHTML(message?.messageText, [ - StarterKit, - Underline, - Highlight, - Mention, - TextStyle, - ]); + useEffect(() => { + const getInfo = async () => { + if (!message?.sender) return; + try { + const res = await getIndividualUserInfo(message?.sender); + if (!res) return null; + setUserInfo(res); + } catch (error) { + // } - }, [message?.editTimestamp]); + }; - const htmlReply = useMemo(() => { - if (reply?.messageText) { - const isHtml = isHtmlString(reply?.messageText); - if (isHtml) return reply?.messageText; - return generateHTML(reply?.messageText, [ - StarterKit, - Underline, - Highlight, - Mention, - TextStyle, - ]); - } - }, [reply?.editTimestamp]); + getInfo(); + }, [message?.sender, getIndividualUserInfo]); - const userAvatarUrl = useMemo(() => { - return message?.senderName - ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${ - message?.senderName - }/qortal_avatar?async=true` - : ''; - }, []); + const htmlText = useMemo(() => { + if (message?.messageText) { + const isHtml = isHtmlString(message?.messageText); + if (isHtml) return message?.messageText; + return generateHTML(message?.messageText, [ + StarterKit, + Underline, + Highlight, + Mention, + TextStyle, + ]); + } + }, [message?.editTimestamp]); - const onSeenFunc = useCallback(() => { - onSeen(message.id); - }, [message?.id]); + const htmlReply = useMemo(() => { + if (reply?.messageText) { + const isHtml = isHtmlString(reply?.messageText); + if (isHtml) return reply?.messageText; + return generateHTML(reply?.messageText, [ + StarterKit, + Underline, + Highlight, + Mention, + TextStyle, + ]); + } + }, [reply?.editTimestamp]); - const theme = useTheme(); - const { t } = useTranslation([ - 'auth', - 'core', - 'group', - 'question', - 'tutorial', - ]); + const userAvatarUrl = useMemo(() => { + return message?.senderName + ? `${getBaseApiReact()}/arbitrary/THUMBNAIL/${ + message?.senderName + }/qortal_avatar?async=true` + : ''; + }, []); - return ( - <> - {message?.divide && ( -
- {t('core:message.generic.unread_messages', { - postProcess: 'capitalizeFirstChar', - })} -
- )} + const onSeenFunc = useCallback(() => { + onSeen(message.id); + }, [message?.id]); - + {message?.divide && ( +
+ {t('core:message.generic.unread_messages', { + postProcess: 'capitalizeFirstChar', + })} +
+ )} + + +
-
+ ) : ( + + + + {message?.senderName?.charAt(0)} + + + + + )} + + - {isShowingAsReply ? ( - - ) : ( + + + + {message?.senderName || message?.sender} + + + - - { + onEdit(message); + }} + > + + + )} + + {!isShowingAsReply && ( + { + onReply(message); }} - alt={message?.senderName} - src={userAvatarUrl} > - {message?.senderName?.charAt(0)} - - - + + + )} + + {!isShowingAsReply && handleReaction && ( + { + if ( + reactions && + reactions[val] && + reactions[val]?.find( + (item) => item?.sender === myAddress + ) + ) { + handleReaction(val, message, false); + } else { + handleReaction(val, message, true); + } + }} + /> + )} + + + {reply && ( + <> + + + { + scrollToItem(replyIndex); + }} + > + + + + + {t('core:message.generic.replied_to', { + person: reply?.senderName || reply?.senderAddress, + postProcess: 'capitalizeFirstChar', + })} + + + {reply?.messageText && ( + + )} + + {reply?.decryptedData?.type === 'notification' ? ( + + ) : ( + + )} + + + + )} + + {htmlText && } + + {message?.decryptedData?.type === 'notification' ? ( + + ) : ( + + )} + {message?.images && messageHasImage(message) && ( + )} - - - {message?.senderName || message?.sender} - - - - - {message?.sender === myAddress && - (!message?.isNotEncrypted || isPrivate === false) && ( + {reactions && + Object.keys(reactions).map((reaction) => { + const numberOfReactions = reactions[reaction]?.length; + if (numberOfReactions === 0) return null; + return ( { - onEdit(message); + key={reaction} + sx={{ + background: theme.palette.background.surface, + borderRadius: '7px', + height: '30px', + minWidth: '45px', + }} + onClick={(event) => { + event.stopPropagation(); // Prevent event bubbling + setAnchorEl(event.currentTarget); + setSelectedReaction(reaction); }} > - +
+ {reaction} +
{' '} + {numberOfReactions > 1 && ( + + {numberOfReactions} + + )}
- )} + ); + })} +
- {!isShowingAsReply && ( - { - onReply(message); + {selectedReaction && ( + { + setAnchorEl(null); + setSelectedReaction(null); + }} + anchorOrigin={{ + vertical: 'top', + horizontal: 'center', + }} + transformOrigin={{ + vertical: 'bottom', + horizontal: 'center', + }} + slotProps={{ + paper: { + style: { + backgroundColor: theme.palette.background.default, + color: theme.palette.text.primary, + }, + }, + }} + > + + + {t('core:message.generic.people_reaction', { + reaction: selectedReaction, + postProcess: 'capitalizeFirstChar', + })} + + + - - - )} + {reactions[selectedReaction]?.map((reactionItem) => ( + + + + ))} + - {!isShowingAsReply && handleReaction && ( - { +
- - )} - - {htmlText && } - - {message?.decryptedData?.type === 'notification' ? ( - - ) : ( - - )} - {message?.images && messageHasImage(message) && ( - + )} - - {reactions && - Object.keys(reactions).map((reaction) => { - const numberOfReactions = reactions[reaction]?.length; - if (numberOfReactions === 0) return null; - return ( - { - event.stopPropagation(); // Prevent event bubbling - setAnchorEl(event.currentTarget); - setSelectedReaction(reaction); - }} - > -
- {reaction} -
{' '} - {numberOfReactions > 1 && ( - - {numberOfReactions} - - )} -
- ); - })} -
- - {selectedReaction && ( - { - setAnchorEl(null); - setSelectedReaction(null); + {message?.isNotEncrypted && isPrivate && ( + - - - {t('core:message.generic.people_reaction', { - reaction: selectedReaction, - postProcess: 'capitalizeFirstChar', - })} - - - - {reactions[selectedReaction]?.map((reactionItem) => ( - - - - ))} - - - - - + /> )} - - {message?.isNotEncrypted && isPrivate && ( - - )} - - {isUpdating ? ( - - {message?.status === 'failed-permanent' - ? t('core:message.error.update_failed', { - postProcess: 'capitalizeFirstChar', - }) - : t('core:message.generic.updating', { - postProcess: 'capitalizeFirstChar', - })} - - ) : isTemp ? ( - - {message?.status === 'failed-permanent' - ? t('core:message.error.send_failed', { - postProcess: 'capitalizeFirstChar', - }) - : t('core:message.generic.sending', { - postProcess: 'capitalizeFirstChar', - })} - - ) : ( - <> - {message?.isEdit && ( - - {t('core:message.generic.edited', { - postProcess: 'capitalizeFirstChar', - })} - - )} - + {isUpdating ? ( + + {message?.status === 'failed-permanent' + ? t('core:message.error.update_failed', { + postProcess: 'capitalizeFirstChar', + }) + : t('core:message.generic.updating', { + postProcess: 'capitalizeFirstChar', + })} + + ) : isTemp ? ( + + {message?.status === 'failed-permanent' + ? t('core:message.error.send_failed', { + postProcess: 'capitalizeFirstChar', + }) + : t('core:message.generic.sending', { + postProcess: 'capitalizeFirstChar', + })} + + ) : ( + <> + {message?.isEdit && ( - {formatTimestamp(message.timestamp)} + {t('core:message.generic.edited', { + postProcess: 'capitalizeFirstChar', + })} - - )} - + )} + + + {formatTimestamp(message.timestamp)} + + + )}
-
- - - ); - } -); + +
+
+ + ); +}; + +const MemoizedMessageItem = memo(MessageItemComponent); +MemoizedMessageItem.displayName = 'MessageItem'; // It ensures React DevTools shows MessageItem as the name (instead of "Anonymous" or "Memo") + +export const MessageItem = MemoizedMessageItem; export const ReplyPreview = ({ message, isEdit = false }) => { const theme = useTheme(); From d24e2ac3bc6a707f074c9b27bbeeeddef68fad08 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Fri, 6 Jun 2025 06:55:32 +0200 Subject: [PATCH 13/23] Add type for Reaction --- src/components/Chat/ChatList.tsx | 11 ++++++++++- src/components/Chat/MessageItem.tsx | 11 ++++++----- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/Chat/ChatList.tsx b/src/components/Chat/ChatList.tsx index 305674c..3d8f84d 100644 --- a/src/components/Chat/ChatList.tsx +++ b/src/components/Chat/ChatList.tsx @@ -7,6 +7,15 @@ import { ChatOptions } from './ChatOptions'; import ErrorBoundary from '../../common/ErrorBoundary'; import { useTranslation } from 'react-i18next'; +type ReactionItem = { + sender: string; + senderName?: string; +}; + +export type ReactionsMap = { + [reactionType: string]: ReactionItem[]; +}; + export const ChatList = ({ initialMessages, myAddress, @@ -236,7 +245,7 @@ export const ChatList = ({ let message = messages[index] || null; // Safeguard against undefined let replyIndex = -1; let reply = null; - let reactions = null; + let reactions: ReactionsMap | null = null; let isUpdating = false; try { diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index c129408..4d412fd 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -53,6 +53,7 @@ import { messageHasImage, } from '../../utils/chat'; import { useTranslation } from 'react-i18next'; +import { ReactionsMap } from './ChatList'; const getBadgeImg = (level) => { switch (level?.toString()) { @@ -105,14 +106,14 @@ type MessageItemProps = { isShowingAsReply?: boolean; isTemp: boolean; isUpdating: boolean; - lastSignature: any; - message: any; - myAddress: any; + lastSignature: string; + message: string; + myAddress: string; onEdit: (messageId: string) => void; onReply: (messageId: string) => void; onSeen: () => void; - reactions: any; // could be null, or type it more strictly - reply: any; // same here + reactions: ReactionsMap | null; + reply: string | null; replyIndex: number; scrollToItem: (index: number) => void; }; From 656cbbde0e1b2d93fbd7948741201478f127d696 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Fri, 6 Jun 2025 07:08:31 +0200 Subject: [PATCH 14/23] Add TextStyle and refactor --- src/components/Chat/ChatList.tsx | 10 +++++----- src/components/Chat/ChatOptions.tsx | 8 ++------ src/components/Chat/TipTap.tsx | 2 +- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/components/Chat/ChatList.tsx b/src/components/Chat/ChatList.tsx index 3d8f84d..0651245 100644 --- a/src/components/Chat/ChatList.tsx +++ b/src/components/Chat/ChatList.tsx @@ -466,13 +466,13 @@ export const ChatList = ({ {enableMentions && (hasSecretKey || isPrivate === false) && ( )} diff --git a/src/components/Chat/ChatOptions.tsx b/src/components/Chat/ChatOptions.tsx index 2d2c352..ef1a4b8 100644 --- a/src/components/Chat/ChatOptions.tsx +++ b/src/components/Chat/ChatOptions.tsx @@ -25,7 +25,6 @@ import { AppsSearchRight, } from '../Apps/Apps-styles'; import IconClearInput from '../../assets/svgs/ClearInput.svg'; -import { CellMeasurerCache } from 'react-virtualized'; import { getBaseApiReact } from '../../App'; import { MessageDisplay } from './MessageDisplay'; import { useVirtualizer } from '@tanstack/react-virtual'; @@ -36,6 +35,7 @@ import { generateHTML } from '@tiptap/react'; import ErrorBoundary from '../../common/ErrorBoundary'; import { useTranslation } from 'react-i18next'; import { isHtmlString } from '../../utils/chat'; +import TextStyle from '@tiptap/extension-text-style'; const extractTextFromHTML = (htmlString = '') => { return convert(htmlString, { @@ -43,11 +43,6 @@ const extractTextFromHTML = (htmlString = '') => { })?.toLowerCase(); }; -const cache = new CellMeasurerCache({ - fixedWidth: true, - defaultHeight: 50, -}); - export const ChatOptions = ({ messages: untransformedMessages, goToMessage, @@ -86,6 +81,7 @@ export const ChatOptions = ({ Underline, Highlight, Mention, + TextStyle, ]); return { ...item, diff --git a/src/components/Chat/TipTap.tsx b/src/components/Chat/TipTap.tsx index 0baef0a..3ab63a1 100644 --- a/src/components/Chat/TipTap.tsx +++ b/src/components/Chat/TipTap.tsx @@ -361,8 +361,8 @@ const MenuBar = memo( ); const extensions = [ + TextStyle, Color.configure({ types: [TextStyle.name, ListItem.name] }), - TextStyle.configure({ types: [ListItem.name] }), StarterKit.configure({ bulletList: { keepMarks: true, From dded49145af691f899a027abbc05db71aa092391 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Fri, 6 Jun 2025 07:30:44 +0200 Subject: [PATCH 15/23] Remove unused function textMatcher --- src/components/Chat/TipTap.tsx | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/components/Chat/TipTap.tsx b/src/components/Chat/TipTap.tsx index 3ab63a1..270dc47 100644 --- a/src/components/Chat/TipTap.tsx +++ b/src/components/Chat/TipTap.tsx @@ -34,16 +34,6 @@ import { fileToBase64 } from '../../utils/fileReading/index.js'; import { useTranslation } from 'react-i18next'; import i18n from 'i18next'; -function textMatcher(doc, from) { - const textBeforeCursor = doc.textBetween(0, from, ' ', ' '); - const match = textBeforeCursor.match(/@[\w]*$/); // Match '@' followed by valid characters - if (!match) return null; - - const start = from - match[0].length; - const query = match[0]; - return { start, query }; -} - const MenuBar = memo( ({ setEditorRef, From 4c8c90a579d59a92c60e49008ba45cb1f296cf57 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Fri, 6 Jun 2025 07:55:22 +0200 Subject: [PATCH 16/23] Define type --- src/components/Chat/TipTap.tsx | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/src/components/Chat/TipTap.tsx b/src/components/Chat/TipTap.tsx index 270dc47..4c69504 100644 --- a/src/components/Chat/TipTap.tsx +++ b/src/components/Chat/TipTap.tsx @@ -1,5 +1,5 @@ import { memo, useCallback, useEffect, useMemo, useRef } from 'react'; -import { EditorProvider, useCurrentEditor } from '@tiptap/react'; +import { Editor, EditorProvider, useCurrentEditor } from '@tiptap/react'; import StarterKit from '@tiptap/starter-kit'; import { Color } from '@tiptap/extension-color'; import ListItem from '@tiptap/extension-list-item'; @@ -373,11 +373,26 @@ const extensions = [ const content = ``; -export default ({ +type TiptapProps = { + setEditorRef: (editorInstance: Editor | null) => void; + onEnter: () => void | Promise; + disableEnter?: boolean; + isChat?: boolean; + maxHeightOffset?: number; + overrideMobile?: boolean; + customEditorHeight?: number | null; + setIsFocusedParent: React.Dispatch>; + isFocusedParent: boolean; + membersWithNames: unknown[]; + enableMentions?: boolean; + insertImage: (image: any) => void; +}; + +const Tiptap = ({ setEditorRef, onEnter, - disableEnter, - isChat, + disableEnter = false, + isChat = false, maxHeightOffset, setIsFocusedParent, isFocusedParent, @@ -386,7 +401,7 @@ export default ({ membersWithNames, enableMentions, insertImage, -}) => { +}: TiptapProps) => { const theme = useTheme(); const [isDisabledEditorEnter, setIsDisabledEditorEnter] = useAtom( isDisabledEditorEnterAtom @@ -613,3 +628,5 @@ export default ({ ); }; + +export default Tiptap; From f0805e1da5ad9ea31d452bce2358de5d206060f5 Mon Sep 17 00:00:00 2001 From: Nicola Benaglia Date: Fri, 6 Jun 2025 07:55:42 +0200 Subject: [PATCH 17/23] Sort properties and add spaces in the code --- src/components/Wallets.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/components/Wallets.tsx b/src/components/Wallets.tsx index 75a36ea..aef22b3 100644 --- a/src/components/Wallets.tsx +++ b/src/components/Wallets.tsx @@ -244,6 +244,7 @@ export const Wallets = ({ setExtState, setRawWallet, rawWallet }) => { )} )} + {wallets?.length > 0 && ( { postProcess: 'capitalizeFirstChar', })} + { if (!seedValue || !seedName || !password) return; onOk({ seedValue, seedName, password }); }} - autoFocus + variant="contained" > {t('core:action.add', { postProcess: 'capitalizeFirstChar', })} + Date: Fri, 6 Jun 2025 08:00:21 +0200 Subject: [PATCH 18/23] Add comment TODO --- src/background/background.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/background/background.ts b/src/background/background.ts index fce3d83..3c688ff 100644 --- a/src/background/background.ts +++ b/src/background/background.ts @@ -1281,7 +1281,6 @@ export async function addUserSettings({ keyValue }) { getData(`${address}-userSettings`) .then((storedData) => { storedData = storedData || {}; // Initialize if no data found - storedData[key] = value; // Update the key-value pair within stored data // Save updated structure back to localStorage @@ -3393,7 +3392,7 @@ const checkGroupList = async () => { .filter( (item) => item?.name !== 'extension-proxy' && - item?.address !== 'QSMMGSgysEuqDCuLw3S4cHrQkBrh3vP3VH' + item?.address !== 'QSMMGSgysEuqDCuLw3S4cHrQkBrh3vP3VH' // TODO put address in a specific file ) .sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0)); @@ -3447,6 +3446,7 @@ export const checkNewMessages = async () => { const latestMessage = responseData.filter( (pub) => pub?.name !== myName )[0]; + if (!latestMessage) { return; // continue to the next group } From 203f14799fd53d7251de448abd3c10144b47db3b Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 5 Jun 2025 01:53:49 +0300 Subject: [PATCH 19/23] fix chat edits with image --- src/components/Chat/ChatGroup.tsx | 68 ++++++++++++++++------------ src/components/Chat/ChatList.tsx | 21 ++------- src/components/Chat/MessageItem.tsx | 4 +- src/components/Embeds/ImageEmbed.tsx | 17 ++++++- src/i18n/locales/de/core.json | 1 + src/i18n/locales/en/core.json | 1 + src/i18n/locales/es/core.json | 1 + src/i18n/locales/fr/core.json | 1 + src/i18n/locales/it/core.json | 1 + src/i18n/locales/ja/core.json | 1 + src/i18n/locales/ru/core.json | 1 + src/i18n/locales/zh/core.json | 1 + 12 files changed, 70 insertions(+), 48 deletions(-) diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx index be0f7c4..a0efe00 100644 --- a/src/components/Chat/ChatGroup.tsx +++ b/src/components/Chat/ChatGroup.tsx @@ -299,17 +299,15 @@ export const ChatGroup = ({ const formatted = combineUIAndExtensionMsgs .filter((rawItem) => !rawItem?.chatReference) .map((item) => { - const message = ( -

- {t('group:message.generic.group_key_created', { - postProcess: 'capitalizeFirstChar', - })} -

- ); const additionalFields = item?.data === 'NDAwMQ==' // TODO put magic string somewhere in a file ? { - text: message, + text: `

${t( + 'group:message.generic.group_key_created', + { + postProcess: 'capitalizeFirstChar', + } + )}

`, } : {}; return { @@ -450,17 +448,15 @@ export const ChatGroup = ({ const formatted = combineUIAndExtensionMsgs .filter((rawItem) => !rawItem?.chatReference) .map((item) => { - const message = ( -

- {t('group:message.generic.group_key_created', { - postProcess: 'capitalizeFirstChar', - })} -

- ); const additionalFields = item?.data === 'NDAwMQ==' ? { - text: message, + text: `

${t( + 'group:message.generic.group_key_created', + { + postProcess: 'capitalizeFirstChar', + } + )}

`, } : {}; const divide = @@ -818,13 +814,28 @@ export const ChatGroup = ({ ); pauseAllQueues(); if (editorRef.current) { - const htmlContent = editorRef.current.getHTML(); - - if (!htmlContent?.trim() || htmlContent?.trim() === '

') return; + let htmlContent = editorRef.current.getHTML(); + const deleteImage = + onEditMessage && isDeleteImage && messageHasImage(onEditMessage); + const hasImage = + chatImagesToSave?.length > 0 || onEditMessage?.images?.length > 0; + if ( + (!htmlContent?.trim() || htmlContent?.trim() === '

') && + !hasImage && + !deleteImage + ) + return; + if (htmlContent?.trim() === '

') { + htmlContent = null; + } setIsSending(true); const message = - isPrivate === false ? editorRef.current.getJSON() : htmlContent; + isPrivate === false + ? !htmlContent + ? '

' + : editorRef.current.getJSON() + : htmlContent; const secretKeyObject = await getSecretKey(false, true); let repliedTo = replyMessage?.signature; @@ -849,8 +860,6 @@ export const ChatGroup = ({ } const imagesToPublish: ImageToPublish[] = []; - const deleteImage = - onEditMessage && isDeleteImage && messageHasImage(onEditMessage); if (deleteImage) { const fee = await getFee('ARBITRARY'); @@ -931,7 +940,6 @@ export const ChatGroup = ({ [isPrivate ? 'message' : 'messageText']: message, version: 3, }; - const message64: any = await objectToBase64(objectMessage); const encryptSingle = @@ -1042,11 +1050,15 @@ export const ChatGroup = ({ const onEdit = useCallback((message) => { setOnEditMessage(message); setReplyMessage(null); - editorRef.current - .chain() - .focus() - .setContent(message?.messageText || message?.text) - .run(); + try { + editorRef.current + .chain() + .focus() + .setContent(message?.messageText || message?.text || '

') + .run(); + } catch (error) { + console.error(error); + } }, []); const handleReaction = useCallback( diff --git a/src/components/Chat/ChatList.tsx b/src/components/Chat/ChatList.tsx index 0651245..e7897c2 100644 --- a/src/components/Chat/ChatList.tsx +++ b/src/components/Chat/ChatList.tsx @@ -289,31 +289,18 @@ export const ChatList = ({ reactions = chatReferences[message.signature]?.reactions || null; - if ( - chatReferences[message.signature]?.edit?.message && - message?.text - ) { + if (chatReferences[message.signature]?.edit) { message.text = chatReferences[message.signature]?.edit?.message; - message.isEdit = true; - message.editTimestamp = - chatReferences[message.signature]?.edit?.timestamp; - } - if ( - chatReferences[message.signature]?.edit?.messageText && - message?.messageText - ) { message.messageText = chatReferences[message.signature]?.edit?.messageText; + message.images = + chatReferences[message.signature]?.edit?.images; + message.isEdit = true; message.editTimestamp = chatReferences[message.signature]?.edit?.timestamp; } - if (chatReferences[message.signature]?.edit?.images) { - message.images = - chatReferences[message.signature]?.edit?.images; - message.isEdit = true; - } } // Check if message is updating diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index 4d412fd..aeb2628 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -47,6 +47,7 @@ import level8Img from '../../assets/badges/level-8.png'; import level9Img from '../../assets/badges/level-9.png'; import level10Img from '../../assets/badges/level-10.png'; import { Embed } from '../Embeds/Embed'; +import CommentsDisabledIcon from '@mui/icons-material/CommentsDisabled'; import { buildImageEmbedLink, isHtmlString, @@ -660,6 +661,7 @@ export const ReplyPreview = ({ message, isEdit = false }) => { ]); const replyMessageText = useMemo(() => { + if (!message?.messageText) return null; const isHtml = isHtmlString(message?.messageText); if (isHtml) return message?.messageText; return generateHTML(message?.messageText, [ @@ -715,7 +717,7 @@ export const ReplyPreview = ({ message, isEdit = false }) => {
)} - {message?.messageText && ( + {message?.replyMessageText && ( )} diff --git a/src/components/Embeds/ImageEmbed.tsx b/src/components/Embeds/ImageEmbed.tsx index 13ec3e2..7402039 100644 --- a/src/components/Embeds/ImageEmbed.tsx +++ b/src/components/Embeds/ImageEmbed.tsx @@ -52,6 +52,8 @@ export const ImageCard = ({ backgroundColor: theme.palette.background.default, height: height, transition: 'height 0.6s ease-in-out', + display: 'flex', + flexDirection: 'column', }} > - - + + @@ -211,6 +223,7 @@ export function ImageViewer({ src = null, alt = '' }) { display: 'flex', justifyContent: 'center', maxWidth: '100%', // Prevent horizontal overflow + height: '100%', }} onClick={handleOpenFullscreen} > diff --git a/src/i18n/locales/de/core.json b/src/i18n/locales/de/core.json index 420894d..cc3a4e6 100644 --- a/src/i18n/locales/de/core.json +++ b/src/i18n/locales/de/core.json @@ -245,6 +245,7 @@ "no_data_image": "Keine Daten für das Bild", "no_description": "Keine Beschreibung", "no_messages": "Keine Nachrichten", + "no_message": "keine nachricht", "no_minting_details": "müngungsdetails auf dem Gateway können nicht angezeigt werden", "no_notifications": "Keine neuen Benachrichtigungen", "no_payments": "Keine Zahlungen", diff --git a/src/i18n/locales/en/core.json b/src/i18n/locales/en/core.json index 49a97e8..a5ca275 100644 --- a/src/i18n/locales/en/core.json +++ b/src/i18n/locales/en/core.json @@ -249,6 +249,7 @@ "no_data_image": "no data for image", "no_description": "no description", "no_messages": "no messages", + "no_message": "no message", "no_minting_details": "cannot view minting details on the gateway", "no_notifications": "no new notifications", "no_payments": "no payments", diff --git a/src/i18n/locales/es/core.json b/src/i18n/locales/es/core.json index f37b46f..39e7e7c 100644 --- a/src/i18n/locales/es/core.json +++ b/src/i18n/locales/es/core.json @@ -246,6 +246,7 @@ "no_data_image": "no hay datos para la imagen", "no_description": "sin descripción", "no_messages": "sin mensajes", + "no_message": "sin mensaje", "no_minting_details": "no se puede ver los detalles de acuñado en la puerta de enlace", "no_notifications": "no hay nuevas notificaciones", "no_payments": "sin pagos", diff --git a/src/i18n/locales/fr/core.json b/src/i18n/locales/fr/core.json index a6d0fa1..76c028b 100644 --- a/src/i18n/locales/fr/core.json +++ b/src/i18n/locales/fr/core.json @@ -247,6 +247,7 @@ "no_data_image": "aucune donnée pour l'image", "no_description": "aucune description", "no_messages": "pas de messages", + "no_message": "aucun message", "no_minting_details": "impossible d'afficher les détails de la passerelle sur la passerelle", "no_notifications": "pas de nouvelles notifications", "no_payments": "aucun paiement", diff --git a/src/i18n/locales/it/core.json b/src/i18n/locales/it/core.json index 9bff9a5..49794a4 100644 --- a/src/i18n/locales/it/core.json +++ b/src/i18n/locales/it/core.json @@ -249,6 +249,7 @@ "no_data_image": "nessun dato per l'immagine", "no_description": "nessuna descrizione", "no_messages": "nessun messaggio", + "no_message": "nessun messaggio", "no_minting_details": "impossibile visualizzare i dettagli di minire sul gateway", "no_notifications": "nessuna nuova notifica", "no_payments": "nessun pagamento", diff --git a/src/i18n/locales/ja/core.json b/src/i18n/locales/ja/core.json index b2b2ae0..d39a89b 100644 --- a/src/i18n/locales/ja/core.json +++ b/src/i18n/locales/ja/core.json @@ -246,6 +246,7 @@ "no_data_image": "画像のデータはありません", "no_description": "説明なし", "no_messages": "メッセージはありません", + "no_message": "メッセージなし", "no_minting_details": "ゲートウェイでミントの詳細を表示できません", "no_notifications": "新しい通知はありません", "no_payments": "支払いなし", diff --git a/src/i18n/locales/ru/core.json b/src/i18n/locales/ru/core.json index e90fb33..aed5ce5 100644 --- a/src/i18n/locales/ru/core.json +++ b/src/i18n/locales/ru/core.json @@ -247,6 +247,7 @@ "no_data_image": "Нет данных для изображения", "no_description": "Нет описания", "no_messages": "Нет сообщений", + "no_message": "нет сообщения", "no_minting_details": "Не могу просматривать детали маттинга на шлюзе", "no_notifications": "Нет новых уведомлений", "no_payments": "Нет платежей", diff --git a/src/i18n/locales/zh/core.json b/src/i18n/locales/zh/core.json index 5208c36..74f2e74 100644 --- a/src/i18n/locales/zh/core.json +++ b/src/i18n/locales/zh/core.json @@ -246,6 +246,7 @@ "no_data_image": "没有图像数据", "no_description": "没有描述", "no_messages": "没有消息", + "no_message": "没有消息", "no_minting_details": "无法在网关上查看薄荷细节", "no_notifications": "没有新的通知", "no_payments": "无付款", From 2915766a26973adf1bfc151ba84c227313cc46ea Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 5 Jun 2025 16:11:33 +0300 Subject: [PATCH 20/23] fix --- src/components/Chat/MessageItem.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index aeb2628..f1cfb40 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -717,9 +717,7 @@ export const ReplyPreview = ({ message, isEdit = false }) => { )} - {message?.replyMessageText && ( - - )} + {replyMessageText && } {message?.decryptedData?.type === 'notification' ? ( From f6c6480fe8f6d001f4bcac67a4644d1c4a546c02 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Thu, 5 Jun 2025 18:10:58 +0300 Subject: [PATCH 21/23] change scroll down style --- src/components/Chat/ChatList.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/Chat/ChatList.tsx b/src/components/Chat/ChatList.tsx index e7897c2..f5cb3f6 100644 --- a/src/components/Chat/ChatList.tsx +++ b/src/components/Chat/ChatList.tsx @@ -429,14 +429,14 @@ export const ChatList = ({