mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "عنوان العملة الرقمية ينتمي إلى حساب آخر.",
|
||||
"enter_crypto_address": "الرجاء إدخال عنوان كريبتو صالح.",
|
||||
"crypto_address_not_resolved": "لم يتم حل عنوان العملة الرقمية بعد.",
|
||||
"copy_message_etherscan": "انسخ الرسالة للتوقيع على <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> رسالة للتوقيع على <2>etherscan</2>",
|
||||
"account": "حساب",
|
||||
"crypto_address": "عنوان العملة الرقمية",
|
||||
"crypto_wallets": "محافظ العملات الرقمية",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "ক্রিপ্টো ঠিকানা অন্য একটি অ্যাকাউন্টের অংশ।",
|
||||
"enter_crypto_address": "দয়া করে একটি বৈধ ক্রিপ্টো ঠিকানা দিন।",
|
||||
"crypto_address_not_resolved": "ক্রিপ্টো ঠিকানা এখনো সমাধান করা হয়নি।",
|
||||
"copy_message_etherscan": "<1>etherscan</1> এ সাইন করার জন্য বার্তা অনুলিপি করুন",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> বার্তা সাইন করতে <2>etherscan</2> এ",
|
||||
"account": "অ্যাকাউন্ট",
|
||||
"crypto_address": "ক্রিপ্টো ঠিকানা",
|
||||
"crypto_wallets": "ক্রিপ্টো ওয়ালেট",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Adresa kryptoměny patří k jinému účtu.",
|
||||
"enter_crypto_address": "Zadejte prosím platnou kryptoadresu.",
|
||||
"crypto_address_not_resolved": "Adresa kryptoměny ještě není vyřešena.",
|
||||
"copy_message_etherscan": "Zkopírujte zprávu k podpisu na <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> zpráva k podpisu na <2>etherscan</2>",
|
||||
"account": "účet",
|
||||
"crypto_address": "adresa kryptoměny",
|
||||
"crypto_wallets": "Kryptoměny",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Crypto-adresse tilhører en anden konto.",
|
||||
"enter_crypto_address": "Indtast venligst en gyldig krypto adresse.",
|
||||
"crypto_address_not_resolved": "Crypto-adresse er endnu ikke løst.",
|
||||
"copy_message_etherscan": "Kopiér besked til at underskrive på <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> besked at underskrive på <2>etherscan</2>",
|
||||
"account": "konto",
|
||||
"crypto_address": "kryptoadresse",
|
||||
"crypto_wallets": "Crypto Wallets",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Krypto-Adresse gehört zu einem anderen Konto.",
|
||||
"enter_crypto_address": "Bitte geben Sie eine gültige Krypto-Adresse ein.",
|
||||
"crypto_address_not_resolved": "Krypto-Adresse ist noch nicht aufgelöst.",
|
||||
"copy_message_etherscan": "Nachricht zum Signieren auf <1>etherscan</1> kopieren",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> Nachricht zum Signieren auf <2>etherscan</2>",
|
||||
"account": "Konto",
|
||||
"crypto_address": "Krypto-Adresse",
|
||||
"crypto_wallets": "Krypto-Wallets",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Η διεύθυνση κρυπτονομίσματος ανήκει σε άλλο λογαριασμό.",
|
||||
"enter_crypto_address": "Παρακαλώ εισαγάγετε μια έγκυρη διεύθυνση κρυπτονομίσματος.",
|
||||
"crypto_address_not_resolved": "Η διεύθυνση κρυπτονομίσματος δεν έχει ακόμη επιλυθεί.",
|
||||
"copy_message_etherscan": "Αντιγράψτε το μήνυμα για υπογραφή στο <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> μήνυμα για υπογραφή στο <2>etherscan</2>",
|
||||
"account": "λογαριασμός",
|
||||
"crypto_address": "διεύθυνση κρυπτονομίσματος",
|
||||
"crypto_wallets": "Πορτοφόλια κρυπτονομισμάτων",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Crypto address belongs to another account.",
|
||||
"enter_crypto_address": "Please enter a valid crypto address.",
|
||||
"crypto_address_not_resolved": "Crypto address is not resolved yet.",
|
||||
"copy_message_etherscan": "Copy message to sign on <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> message to sign on <2>etherscan</2>",
|
||||
"account": "account",
|
||||
"crypto_address": "crypto address",
|
||||
"crypto_wallets": "Crypto Wallets",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "La dirección de criptomoneda pertenece a otra cuenta.",
|
||||
"enter_crypto_address": "Por favor, introduzca una dirección de cripto válida.",
|
||||
"crypto_address_not_resolved": "La dirección de criptomoneda aún no está resuelta.",
|
||||
"copy_message_etherscan": "Copiar mensaje para firmar en <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> mensaje para firmar en <2>etherscan</2>",
|
||||
"account": "cuenta",
|
||||
"crypto_address": "dirección de cripto",
|
||||
"crypto_wallets": "Billeteras cripto",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "آدرس رمزارز به حساب دیگری تعلق دارد.",
|
||||
"enter_crypto_address": "لطفاً یک آدرس کریپتو صحیح وارد کنید.",
|
||||
"crypto_address_not_resolved": "آدرس رمزارز هنوز حل نشده است.",
|
||||
"copy_message_etherscan": "پیام را برای امضا در <1>etherscan</1> کپی کنید",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> پیامی برای امضا در <2>etherscan</2>",
|
||||
"account": "حساب",
|
||||
"crypto_address": "آدرس رمزارز",
|
||||
"crypto_wallets": "کیف پول رمز ارز",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Krypto-osoite kuuluu toiseen tiliin.",
|
||||
"enter_crypto_address": "Syötä voimassa oleva krypto-osoite.",
|
||||
"crypto_address_not_resolved": "Krypto-osoitetta ei ole vielä ratkaistu.",
|
||||
"copy_message_etherscan": "Kopioi viesti allekirjoitettavaksi <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> viesti allekirjoitettavaksi <2>etherscan</2>:ssa",
|
||||
"account": "tili",
|
||||
"crypto_address": "krypto-osoite",
|
||||
"crypto_wallets": "Krypto-lompakot",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Ang crypto address ay nauugnay sa ibang account.",
|
||||
"enter_crypto_address": "Mangyaring magpasok ng wastong crypto address.",
|
||||
"crypto_address_not_resolved": "Hindi pa naresolba ang crypto address.",
|
||||
"copy_message_etherscan": "Kopyahin ang mensahe upang pirmahan sa <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> mensahe na pipirmahan sa <2>etherscan</2>",
|
||||
"account": "account",
|
||||
"crypto_address": "crypto address",
|
||||
"crypto_wallets": "Mga Wallet ng Crypto",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "L'adresse crypto appartient à un autre compte.",
|
||||
"enter_crypto_address": "Veuillez entrer une adresse crypto valide.",
|
||||
"crypto_address_not_resolved": "L'adresse crypto n'est pas encore résolue.",
|
||||
"copy_message_etherscan": "Copier le message à signer sur <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> message à signer sur <2>etherscan</2>",
|
||||
"account": "compte",
|
||||
"crypto_address": "adresse crypto",
|
||||
"crypto_wallets": "Portefeuilles de crypto",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "כתובת המטבע הקריפטוגרפי שייכת לחשבון אחר.",
|
||||
"enter_crypto_address": "אנא הזן כתובת קריפטו תקף.",
|
||||
"crypto_address_not_resolved": "כתובת המטבע הקריפטוגרפית עדיין לא נפתרה.",
|
||||
"copy_message_etherscan": "העתק הודעה לחתימה ב-<1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> הודעה לחתימה ב-<2>etherscan</2>",
|
||||
"account": "חשבון",
|
||||
"crypto_address": "כתובת קריפטו",
|
||||
"crypto_wallets": "ארנק קריפטו",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "क्रिप्टो पता दूसरे खाते से संबंधित है।",
|
||||
"enter_crypto_address": "कृपया एक मान्य क्रिप्टो पता दर्ज करें।",
|
||||
"crypto_address_not_resolved": "क्रिप्टो पता अभी तक हल नहीं किया गया है।",
|
||||
"copy_message_etherscan": "<1>etherscan</1> पर साइन करने के लिए संदेश कॉपी करें",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> संदेश को <2>etherscan</2> पर हस्ताक्षर करें",
|
||||
"account": "खाता",
|
||||
"crypto_address": "क्रिप्टो पता",
|
||||
"crypto_wallets": "क्रिप्टो वॉलेट्स",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "A kripto-cím egy másik fiókhoz tartozik.",
|
||||
"enter_crypto_address": "Kérjük, adjon meg egy érvényes kripto címet.",
|
||||
"crypto_address_not_resolved": "A kripto cím még nem oldódott meg.",
|
||||
"copy_message_etherscan": "Üzenet másolása aláíráshoz a <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> üzenet aláírásához az <2>etherscan</2>-en",
|
||||
"account": "fiók",
|
||||
"crypto_address": "kriptovaluta cím",
|
||||
"crypto_wallets": "Kriptopénztárcák",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Alamat kripto milik akun lain.",
|
||||
"enter_crypto_address": "Harap masukkan alamat kripto yang valid.",
|
||||
"crypto_address_not_resolved": "Alamat kripto belum terpecahkan.",
|
||||
"copy_message_etherscan": "Salin pesan untuk ditandatangani di <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> pesan untuk ditandatangani di <2>etherscan</2>",
|
||||
"account": "akun",
|
||||
"crypto_address": "alamat kripto",
|
||||
"crypto_wallets": "Dompet Crypto",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "L'indirizzo cripto appartiene a un altro account.",
|
||||
"enter_crypto_address": "Inserisci un indirizzo cripto valido.",
|
||||
"crypto_address_not_resolved": "L'indirizzo cripto non è ancora risolto.",
|
||||
"copy_message_etherscan": "Copia messaggio da firmare su <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> messaggio da firmare su <2>etherscan</2>",
|
||||
"account": "account",
|
||||
"crypto_address": "indirizzo crypto",
|
||||
"crypto_wallets": "Wallet crypto",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "暗号通貨アドレスは別のアカウントに属しています。",
|
||||
"enter_crypto_address": "有効な暗号通貨アドレスを入力してください。",
|
||||
"crypto_address_not_resolved": "暗号通貨アドレスはまだ解決されていません。",
|
||||
"copy_message_etherscan": "<1>etherscan</1>で署名するメッセージをコピー",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> メッセージを<2>etherscan</2>でサイン",
|
||||
"account": "アカウント",
|
||||
"crypto_address": "暗号通貨アドレス",
|
||||
"crypto_wallets": "暗号通貨ウォレット",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "암호 주소는 다른 계정에 속합니다.",
|
||||
"enter_crypto_address": "유효한 암호 주소를 입력하세요.",
|
||||
"crypto_address_not_resolved": "암호 주소가 아직 해결되지 않았습니다.",
|
||||
"copy_message_etherscan": "<1>etherscan</1>에서 서명할 메시지 복사",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> 메시지를 <2>etherscan</2>에서 서명",
|
||||
"account": "계정",
|
||||
"crypto_address": "암호 화폐 주소",
|
||||
"crypto_wallets": "암호 화폐 지갑",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "क्रिप्टो पत्ता इतर खात्याशी संबंधित आहे.",
|
||||
"enter_crypto_address": "कृपया मान्य क्रिप्टो पत्ता प्रविष्ट करा.",
|
||||
"crypto_address_not_resolved": "क्रिप्टो पत्ता अजून सोपवला नाही.",
|
||||
"copy_message_etherscan": "<1>etherscan</1> वर स्वाक्षरीसाठी संदेश कॉपी करा",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> संदेश <2>etherscan</2> वर हस्ताक्षर करण्यासाठी",
|
||||
"account": "खाता",
|
||||
"crypto_address": "क्रिप्टो पत्ता",
|
||||
"crypto_wallets": "क्रिप्टो बटव्या",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Crypto-adres behoort tot een andere account.",
|
||||
"enter_crypto_address": "Voer alstublieft een geldig crypto-adres in.",
|
||||
"crypto_address_not_resolved": "Crypto-adres is nog niet opgelost.",
|
||||
"copy_message_etherscan": "Kopieer bericht om te ondertekenen op <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> bericht om te ondertekenen op <2>etherscan</2>",
|
||||
"account": "account",
|
||||
"crypto_address": "crypto adres",
|
||||
"crypto_wallets": "Crypto-portefeuilles",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Crypto-adressen tilhører en annen konto.",
|
||||
"enter_crypto_address": "Vennligst skriv inn en gyldig kryptoadresse.",
|
||||
"crypto_address_not_resolved": "Crypto-adresse er ikke løst ennå.",
|
||||
"copy_message_etherscan": "Kopier melding for å signere på <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> melding å signere på <2>etherscan</2>",
|
||||
"account": "konto",
|
||||
"crypto_address": "kryptoadresse",
|
||||
"crypto_wallets": "Crypto-lommebøker",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Adres kryptowaluty należy do innego konta.",
|
||||
"enter_crypto_address": "Proszę podać prawidłowy adres kryptowalutowy.",
|
||||
"crypto_address_not_resolved": "Adres kryptowaluty nie jest jeszcze rozwiązany.",
|
||||
"copy_message_etherscan": "Skopiuj wiadomość do podpisania na <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> wiadomość do podpisania na <2>etherscan</2>",
|
||||
"account": "konto",
|
||||
"crypto_address": "adres kryptowalutowy",
|
||||
"crypto_wallets": "Portfele kryptowalut",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "O endereço de criptomoeda pertence a outra conta.",
|
||||
"enter_crypto_address": "Por favor, insira um endereço de cripto válido.",
|
||||
"crypto_address_not_resolved": "O endereço de criptomoeda ainda não foi resolvido.",
|
||||
"copy_message_etherscan": "Copiar mensagem para assinar no <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> mensagem para assinar no <2>etherscan</2>",
|
||||
"account": "conta",
|
||||
"crypto_address": "endereço de cripto",
|
||||
"crypto_wallets": "Carteiras cripto",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Adresa crypto aparține unui alt cont.",
|
||||
"enter_crypto_address": "Vă rugăm să introduceți o adresă crypto validă.",
|
||||
"crypto_address_not_resolved": "Adresa crypto nu este încă rezolvată.",
|
||||
"copy_message_etherscan": "Copiați mesajul pentru a semna pe <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> mesaj de semnat pe <2>etherscan</2>",
|
||||
"account": "cont",
|
||||
"crypto_address": "adresa cripto",
|
||||
"crypto_wallets": "Portofele crypto",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Адрес криптовалюты принадлежит другому аккаунту.",
|
||||
"enter_crypto_address": "Пожалуйста, введите действительный криптовалютный адрес.",
|
||||
"crypto_address_not_resolved": "Адрес криптовалюты еще не решен.",
|
||||
"copy_message_etherscan": "Скопируйте сообщение для подписи на <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> сообщение для подписи на <2>etherscan</2>",
|
||||
"account": "аккаунт",
|
||||
"crypto_address": "адрес криптовалюты",
|
||||
"crypto_wallets": "Криптокошельки",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Adresa e kriptovalutës i përket një llogarie tjetër.",
|
||||
"enter_crypto_address": "Ju lutemi vendosni një adresë kripto të vlefshme.",
|
||||
"crypto_address_not_resolved": "Adresa e kriptovalutës nuk është ende e zgjidhur.",
|
||||
"copy_message_etherscan": "Kopjoni mesazhin për të nënshkruar në <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> mesazh për nënshkrim në <2>etherscan</2>",
|
||||
"account": "llogari",
|
||||
"crypto_address": "adresa e kripto",
|
||||
"crypto_wallets": "Portofolitë e cryptos",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Crypto-adress tillhör ett annat konto.",
|
||||
"enter_crypto_address": "Ange en giltig kryptoadress.",
|
||||
"crypto_address_not_resolved": "Crypto-adressen är ännu inte löst.",
|
||||
"copy_message_etherscan": "Kopiera meddelande för att signera på <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> meddelande att signera på <2>etherscan</2>",
|
||||
"account": "konto",
|
||||
"crypto_address": "kryptoadress",
|
||||
"crypto_wallets": "Crypto-plånböcker",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "క్రిప్టో చిరునామా మరొక ఖాతాకు చేరుకుంది.",
|
||||
"enter_crypto_address": "దయచేసి చేసిన క్రిప్టో చిరునామాను నమోదు చేయండి.",
|
||||
"crypto_address_not_resolved": "క్రిప్టో చిరునామా ఇంకా పరిష్కరించబడలేదు.",
|
||||
"copy_message_etherscan": "<1>etherscan</1> లో సైన్ చేయడానికి సందేశాన్ని కాపీ చేయండి",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> సందేశం సాఇన్ చేయడానికి <2>etherscan</2> మీద",
|
||||
"account": "ఖాతా",
|
||||
"crypto_address": "క్రిప్టో చిరునామా",
|
||||
"crypto_wallets": "క్రిప్టో వాలెట్లు",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "ที่อยู่คริปโตเป็นของบัญชีอื่น",
|
||||
"enter_crypto_address": "โปรดป้อนที่อยู่คริปโตที่ถูกต้อง",
|
||||
"crypto_address_not_resolved": "ที่อยู่คริปโตยังไม่ได้รับการแก้ไข",
|
||||
"copy_message_etherscan": "คัดลอกข้อความเพื่อลงนามบน <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> ข้อความเพื่อเซ็นบน <2>etherscan</2>",
|
||||
"account": "บัญชี",
|
||||
"crypto_address": "ที่อยู่คริปโต",
|
||||
"crypto_wallets": "กระเป๋าเงินดิจิทัล",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Kripto adres başka bir hesaba aittir.",
|
||||
"enter_crypto_address": "Lütfen geçerli bir kripto adresi girin.",
|
||||
"crypto_address_not_resolved": "Kripto adres henüz çözülmedi.",
|
||||
"copy_message_etherscan": "<1>etherscan</1> üzerinde imzalamak için mesajı kopyala",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> <2>etherscan</2> üzerinde imzalanacak mesaj",
|
||||
"account": "hesap",
|
||||
"crypto_address": "kripto adres",
|
||||
"crypto_wallets": "Kripto Cüzdanlar",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Адреса криптовалюти належить до іншого облікового запису.",
|
||||
"enter_crypto_address": "Будь ласка, введіть дійсну криптовалютну адресу.",
|
||||
"crypto_address_not_resolved": "Адреса криптовалюти ще не вирішена.",
|
||||
"copy_message_etherscan": "Скопіюйте повідомлення для підпису на <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> повідомлення для підпису на <2>etherscan</2>",
|
||||
"account": "рахунок",
|
||||
"crypto_address": "адреса криптовалюти",
|
||||
"crypto_wallets": "Криптокошельки",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "کرپٹو پتا دوسرے اکاؤنٹ کا حصہ ہے۔",
|
||||
"enter_crypto_address": "براہ کرم ایک معتبر کرپٹو پتہ داخل کریں۔",
|
||||
"crypto_address_not_resolved": "کرپٹو پتا ابھی تک حل نہیں ہوا ہے۔",
|
||||
"copy_message_etherscan": "<1>etherscan</1> پر دستخط کرنے کے لیے پیغام کاپی کریں",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> پیغام <2>etherscan</2> پر دستخط کریں",
|
||||
"account": "اکاؤنٹ",
|
||||
"crypto_address": "کرپٹو پتہ",
|
||||
"crypto_wallets": "کرپٹو والٹس",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "Địa chỉ tiền điện tử thuộc về một tài khoản khác.",
|
||||
"enter_crypto_address": "Vui lòng nhập địa chỉ tiền điện tử hợp lệ.",
|
||||
"crypto_address_not_resolved": "Địa chỉ tiền điện tử vẫn chưa được giải quyết.",
|
||||
"copy_message_etherscan": "Sao chép tin nhắn để ký trên <1>etherscan</1>",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> tin nhắn để ký trên <2>etherscan</2>",
|
||||
"account": "tài khoản",
|
||||
"crypto_address": "địa chỉ tiền điện tử",
|
||||
"crypto_wallets": "Ví tiền điện tử",
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"crypto_address_not_yours": "加密地址属于另一个帐户。",
|
||||
"enter_crypto_address": "请输入有效的加密货币地址。",
|
||||
"crypto_address_not_resolved": "加密地址尚未解析。",
|
||||
"copy_message_etherscan": "复制消息以在<1>etherscan</1>上签名",
|
||||
"copy_message_etherscan": "<1>{{copy}}</1> 在<2>etherscan</2>上签名的消息",
|
||||
"account": "帐户",
|
||||
"crypto_address": "加密地址",
|
||||
"crypto_wallets": "加密货币钱包",
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||
import { Comment, useComment } from '@plebbit/plebbit-react-hooks';
|
||||
import { useFloating, offset, size, autoUpdate, Placement } from '@floating-ui/react';
|
||||
import { fetchWebpageThumbnailIfNeeded, getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
||||
import { getHasThumbnail } from '../../lib/utils/media-utils';
|
||||
import { getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||
@@ -18,6 +18,7 @@ import PostMenuDesktop from '../post-desktop/post-menu-desktop';
|
||||
import styles from './catalog-row.module.css';
|
||||
import _ from 'lodash';
|
||||
import { ContentPreview } from '../../views/home/popular-threads-box';
|
||||
import { useCommentMediaInfo } from '../../hooks/use-comment-media-info';
|
||||
|
||||
interface CatalogPostMediaProps {
|
||||
commentMediaInfo: any;
|
||||
@@ -116,22 +117,9 @@ const CatalogPost = ({ post }: { post: Comment }) => {
|
||||
} = post || {};
|
||||
const linkCount = useCountLinksInReplies(post);
|
||||
|
||||
// some sites have CORS access, so the thumbnail can be fetched client-side, which is helpful if subplebbit.settings.fetchThumbnailUrls is false
|
||||
const initialCommentMediaInfo = useMemo(() => getCommentMediaInfo(post), [post]);
|
||||
const [commentMediaInfo, setCommentMediaInfo] = useState(initialCommentMediaInfo);
|
||||
const commentMediaInfo = useCommentMediaInfo(post);
|
||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||
|
||||
const fetchThumbnail = useCallback(async () => {
|
||||
if (initialCommentMediaInfo?.type === 'webpage' && !initialCommentMediaInfo.thumbnail) {
|
||||
const newMediaInfo = await fetchWebpageThumbnailIfNeeded(initialCommentMediaInfo);
|
||||
setCommentMediaInfo(newMediaInfo);
|
||||
}
|
||||
}, [initialCommentMediaInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchThumbnail();
|
||||
}, [fetchThumbnail]);
|
||||
|
||||
const { hidden } = useHide({ cid });
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
@@ -29,18 +29,14 @@ interface MediaProps {
|
||||
const Thumbnail = ({ commentMediaInfo, deleted, displayHeight, displayWidth, isFloatingEmbed, isOutOfFeed, isReply, removed, spoiler, setShowThumbnail }: MediaProps) => {
|
||||
const isMobile = useIsMobile();
|
||||
const { patternThumbnailUrl, thumbnail, type, url } = commentMediaInfo || {};
|
||||
const [hasError, setHasError] = useState(false);
|
||||
const handleError = () => setHasError(true);
|
||||
|
||||
let thumbnailComponent: React.ReactNode = null;
|
||||
const iframeThumbnail = patternThumbnailUrl || thumbnail;
|
||||
const gifFrameUrl = useFetchGifFirstFrame(type === 'gif' ? url : undefined);
|
||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, url);
|
||||
|
||||
if (type === 'gif' && gifFrameUrl) {
|
||||
thumbnailComponent = <img src={gifFrameUrl} alt='' onClick={() => setShowThumbnail(false)} />;
|
||||
} else if (type === 'image') {
|
||||
thumbnailComponent = <img src={url} alt='' onError={handleError} onClick={() => setShowThumbnail(false)} />;
|
||||
if (type === 'gif') {
|
||||
thumbnailComponent = <img src={gifFrameUrl || url} alt='' onClick={() => setShowThumbnail(false)} />;
|
||||
} else if (type === 'video') {
|
||||
thumbnailComponent = thumbnail ? (
|
||||
<img src={thumbnail} alt='' />
|
||||
@@ -61,7 +57,7 @@ const Thumbnail = ({ commentMediaInfo, deleted, displayHeight, displayWidth, isF
|
||||
|
||||
const linkWithoutThumbnail = url && new URL(url);
|
||||
|
||||
return hasError || deleted || removed ? (
|
||||
return deleted || removed ? (
|
||||
<img className={styles.fileDeleted} src='assets/filedeleted-res.gif' alt='File deleted' />
|
||||
) : spoiler ? (
|
||||
<img className={styles.spoiler} src='assets/spoiler.png' alt='' onClick={() => setShowThumbnail(false)} />
|
||||
@@ -106,8 +102,6 @@ const Media = ({ commentMediaInfo, isReply, setShowThumbnail }: MediaProps) => {
|
||||
<img src={url} alt='' onClick={() => setShowThumbnail(true)} />
|
||||
) : type === 'video' ? (
|
||||
<video src={url} controls autoPlay loop muted />
|
||||
) : type === 'image' ? (
|
||||
<img src={url} alt='' onClick={() => setShowThumbnail(true)} />
|
||||
) : type === 'webpage' ? (
|
||||
<img src={thumbnail} alt='' onClick={() => setShowThumbnail(true)} />
|
||||
) : null}
|
||||
@@ -131,12 +125,75 @@ const Media = ({ commentMediaInfo, isReply, setShowThumbnail }: MediaProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
interface ImageProps {
|
||||
commentMediaInfo: CommentMediaInfo;
|
||||
displayHeight: string;
|
||||
displayWidth: string;
|
||||
isOutOfFeed: boolean;
|
||||
post: Comment | undefined;
|
||||
}
|
||||
|
||||
const Image = ({ commentMediaInfo, displayHeight, displayWidth, isOutOfFeed, post }: ImageProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { parentCid } = post || {};
|
||||
const { type, url } = commentMediaInfo || {};
|
||||
const isReply = parentCid;
|
||||
const isMobile = useIsMobile();
|
||||
const [isImageExpanded, setIsImageExpanded] = useState(false);
|
||||
const { fitExpandedImagesToScreen } = useExpandedMediaStore();
|
||||
const mediaDimensions = getMediaDimensions(commentMediaInfo);
|
||||
const mediaClass = `${isMobile ? styles.mediaMobile : isReply ? styles.mediaDesktopReply : styles.mediaDesktopOp} ${
|
||||
fitExpandedImagesToScreen ? styles.fitToScreen : ''
|
||||
}`;
|
||||
const thumbnailSmallPadding = isMobile ? styles.thumbnailMobile : styles.thumbnailReplyDesktop;
|
||||
const thumbnailDimensions = { '--width': displayWidth, '--height': displayHeight } as React.CSSProperties;
|
||||
|
||||
const [hasError, setHasError] = useState(false);
|
||||
const handleError = () => setHasError(true);
|
||||
|
||||
return isMobile ? (
|
||||
<span className={styles.thumbnail}>
|
||||
<span
|
||||
className={isImageExpanded ? mediaClass : `${isOutOfFeed ? styles.subplebbitAvatar : styles.thumbnailSmall} ${thumbnailSmallPadding}`}
|
||||
style={isImageExpanded ? {} : thumbnailDimensions}
|
||||
>
|
||||
{hasError ? (
|
||||
<img src='assets/filedeleted-res.gif' alt='File deleted' />
|
||||
) : (
|
||||
<img src={url} onError={handleError} alt='' onClick={() => setIsImageExpanded(!isImageExpanded)} />
|
||||
)}
|
||||
</span>
|
||||
{isImageExpanded && type && (
|
||||
<div className={styles.fileInfo}>
|
||||
<a href={url} target='_blank' rel='noopener noreferrer'>
|
||||
{url && url.length > 30 ? url.slice(0, 30) + '...' : url}
|
||||
</a>{' '}
|
||||
({getDisplayMediaInfoType(type, t)}
|
||||
{mediaDimensions && `, ${mediaDimensions}`})
|
||||
</div>
|
||||
)}
|
||||
{type && !isImageExpanded && <div className={styles.fileInfo}>{`${post?.spoiler ? `${t('spoiler')} - ` : ''} ${getDisplayMediaInfoType(type, t)}`}</div>}
|
||||
</span>
|
||||
) : (
|
||||
<span
|
||||
className={isImageExpanded ? mediaClass : `${isOutOfFeed ? styles.subplebbitAvatar : styles.thumbnailBig} ${styles.thumbnail}`}
|
||||
style={isImageExpanded ? {} : thumbnailDimensions}
|
||||
>
|
||||
{hasError ? (
|
||||
<img src='assets/filedeleted-res.gif' alt='File deleted' />
|
||||
) : (
|
||||
<img src={url} onError={handleError} alt='' onClick={() => setIsImageExpanded(!isImageExpanded)} />
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const CommentMedia = ({ commentMediaInfo, isFloatingEmbed, post, showThumbnail, setShowThumbnail }: MediaProps) => {
|
||||
const { deleted, linkHeight, linkWidth, parentCid, removed, spoiler } = post || {};
|
||||
const isReply = parentCid;
|
||||
const { t } = useTranslation();
|
||||
const isMobile = useIsMobile();
|
||||
const { url } = commentMediaInfo || {};
|
||||
const { thumbnailHeight, thumbnailWidth, url } = commentMediaInfo || {};
|
||||
let type = commentMediaInfo?.type;
|
||||
const gifFrameUrl = useFetchGifFirstFrame(url);
|
||||
|
||||
@@ -150,10 +207,17 @@ const CommentMedia = ({ commentMediaInfo, isFloatingEmbed, post, showThumbnail,
|
||||
const maxThumbnailSize = isMobile || isReply ? 125 : 250;
|
||||
|
||||
if (linkWidth && linkHeight) {
|
||||
// use the dimensions from the plebbit-js api
|
||||
let scale = Math.min(1, maxThumbnailSize / Math.max(linkWidth, linkHeight));
|
||||
displayWidth = `${linkWidth * scale}px`;
|
||||
displayHeight = `${linkHeight * scale}px`;
|
||||
} else if (thumbnailHeight && thumbnailWidth) {
|
||||
// use the dimensions from the thumbnail fetched by useCommentMediaInfo
|
||||
let scale = Math.min(1, maxThumbnailSize / Math.max(thumbnailWidth, thumbnailHeight));
|
||||
displayWidth = `${thumbnailWidth * scale}px`;
|
||||
displayHeight = `${thumbnailHeight * scale}px`;
|
||||
} else {
|
||||
// use the default size
|
||||
displayWidth = `${maxThumbnailSize}px`;
|
||||
displayHeight = `${maxThumbnailSize}px`;
|
||||
}
|
||||
@@ -165,45 +229,11 @@ const CommentMedia = ({ commentMediaInfo, isFloatingEmbed, post, showThumbnail,
|
||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||
const isOutOfFeed = isDescription || isRules || isFloatingEmbed; // virtuoso wrapper unneeded
|
||||
|
||||
const [isImageExpanded, setIsImageExpanded] = useState(false);
|
||||
const { fitExpandedImagesToScreen } = useExpandedMediaStore();
|
||||
const mediaDimensions = getMediaDimensions(commentMediaInfo);
|
||||
const mediaClass = `${isMobile ? styles.mediaMobile : isReply ? styles.mediaDesktopReply : styles.mediaDesktopOp} ${
|
||||
fitExpandedImagesToScreen ? styles.fitToScreen : ''
|
||||
}`;
|
||||
const thumbnailSmallPadding = isMobile ? styles.thumbnailMobile : styles.thumbnailReplyDesktop;
|
||||
const thumbnailDimensions = { '--width': displayWidth, '--height': displayHeight } as React.CSSProperties;
|
||||
|
||||
return (
|
||||
<span className={styles.content}>
|
||||
{commentMediaInfo?.type === 'image' ? (
|
||||
isMobile ? (
|
||||
<span className={styles.thumbnail}>
|
||||
<span
|
||||
className={isImageExpanded ? mediaClass : `${isOutOfFeed ? styles.subplebbitAvatar : styles.thumbnailSmall} ${thumbnailSmallPadding}`}
|
||||
style={isImageExpanded ? {} : thumbnailDimensions}
|
||||
>
|
||||
<img src={url} alt='' onClick={() => setIsImageExpanded(!isImageExpanded)} />
|
||||
</span>
|
||||
{isImageExpanded && type && (
|
||||
<div className={styles.fileInfo}>
|
||||
<a href={url} target='_blank' rel='noopener noreferrer'>
|
||||
{url && url.length > 30 ? url.slice(0, 30) + '...' : url}
|
||||
</a>{' '}
|
||||
({getDisplayMediaInfoType(type, t)}
|
||||
{mediaDimensions && `, ${mediaDimensions}`})
|
||||
</div>
|
||||
)}
|
||||
{type && !isImageExpanded && <div className={styles.fileInfo}>{`${post?.spoiler ? `${t('spoiler')} - ` : ''} ${getDisplayMediaInfoType(type, t)}`}</div>}
|
||||
</span>
|
||||
) : (
|
||||
<span
|
||||
className={isImageExpanded ? mediaClass : `${isOutOfFeed ? styles.subplebbitAvatar : styles.thumbnailBig} ${styles.thumbnail}`}
|
||||
style={isImageExpanded ? {} : thumbnailDimensions}
|
||||
>
|
||||
<img src={url} alt='' onClick={() => setIsImageExpanded(!isImageExpanded)} />
|
||||
</span>
|
||||
)
|
||||
// images just enlarge when clicked, so they don't need two separate components
|
||||
<Image commentMediaInfo={commentMediaInfo} displayHeight={displayHeight} displayWidth={displayWidth} isOutOfFeed={isOutOfFeed} post={post} />
|
||||
) : (
|
||||
<>
|
||||
<span className={`${showThumbnail ? styles.show : styles.hide} ${styles.thumbnail}`}>
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||
import { Comment, useAuthorAvatar, useComment, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||
import styles from '../../views/post/post.module.css';
|
||||
import {
|
||||
CommentMediaInfo,
|
||||
fetchWebpageThumbnailIfNeeded,
|
||||
getCommentMediaInfo,
|
||||
getDisplayMediaInfoType,
|
||||
getHasThumbnail,
|
||||
getMediaDimensions,
|
||||
} from '../../lib/utils/media-utils';
|
||||
import { getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
|
||||
import { hashStringToColor, getTextColorForBackground } from '../../lib/utils/post-utils';
|
||||
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||
import { isValidURL } from '../../lib/utils/url-utils';
|
||||
import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||
import useAvatarVisibilityStore from '../../stores/use-avatar-visibility-store';
|
||||
import useAuthorAddressClick from '../../hooks/use-author-address-click';
|
||||
import { useCommentMediaInfo } from '../../hooks/use-comment-media-info';
|
||||
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
|
||||
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
|
||||
import useHide from '../../hooks/use-hide';
|
||||
@@ -228,23 +222,8 @@ const PostMedia = ({ post }: PostProps) => {
|
||||
};
|
||||
|
||||
const PostMediaContent = ({ post, link, spoiler, t }: { post: any; link: string; spoiler: boolean; t: any }) => {
|
||||
const initialInfo = getCommentMediaInfo(post);
|
||||
const [webpageThumbnail, setWebpageThumbnail] = useState<CommentMediaInfo | undefined>();
|
||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||
|
||||
useEffect(() => {
|
||||
// some sites have CORS access, so the thumbnail can be fetched client-side, which is helpful if subplebbit.settings.fetchThumbnailUrls is false
|
||||
const loadThumbnail = async () => {
|
||||
if (initialInfo?.type === 'webpage' && !initialInfo.thumbnail) {
|
||||
const newMediaInfo = await fetchWebpageThumbnailIfNeeded(initialInfo);
|
||||
setWebpageThumbnail(newMediaInfo);
|
||||
}
|
||||
};
|
||||
loadThumbnail();
|
||||
}, [initialInfo]);
|
||||
|
||||
const commentMediaInfo = webpageThumbnail || initialInfo;
|
||||
|
||||
const commentMediaInfo = useCommentMediaInfo(post);
|
||||
const { url } = commentMediaInfo || {};
|
||||
let type = commentMediaInfo?.type;
|
||||
const gifFrameUrl = useFetchGifFirstFrame(url);
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||
import { Comment, useAuthorAvatar, useComment, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||
import styles from '../../views/post/post.module.css';
|
||||
import { CommentMediaInfo, fetchWebpageThumbnailIfNeeded, getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
||||
import { getHasThumbnail } from '../../lib/utils/media-utils';
|
||||
import { getTextColorForBackground, hashStringToColor } from '../../lib/utils/post-utils';
|
||||
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||
import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||
import useAvatarVisibilityStore from '../../stores/use-avatar-visibility-store';
|
||||
import useAuthorAddressClick from '../../hooks/use-author-address-click';
|
||||
import { useCommentMediaInfo } from '../../hooks/use-comment-media-info';
|
||||
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
|
||||
import useHide from '../../hooks/use-hide';
|
||||
import useReplies from '../../hooks/use-replies';
|
||||
@@ -26,6 +27,7 @@ import _ from 'lodash';
|
||||
const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: PostProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { author, cid, deleted, link, locked, parentCid, pinned, postCid, reason, removed, shortCid, state, subplebbitAddress, timestamp } = post || {};
|
||||
const isReply = parentCid;
|
||||
const title = post?.title?.trim();
|
||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||
const { address, shortAddress } = author || {};
|
||||
@@ -40,27 +42,9 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P
|
||||
const isInPostPageView = isPostPageView(location.pathname, params);
|
||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
|
||||
|
||||
const initialInfo = getCommentMediaInfo(post);
|
||||
const [webpageThumbnail, setWebpageThumbnail] = useState<CommentMediaInfo | undefined>();
|
||||
useEffect(() => {
|
||||
// some sites have CORS access, so the thumbnail can be fetched client-side, which is helpful if subplebbit.settings.fetchThumbnailUrls is false
|
||||
const loadThumbnail = async () => {
|
||||
if (initialInfo?.type === 'webpage' && !initialInfo.thumbnail) {
|
||||
const newMediaInfo = await fetchWebpageThumbnailIfNeeded(initialInfo);
|
||||
setWebpageThumbnail(newMediaInfo);
|
||||
}
|
||||
};
|
||||
loadThumbnail();
|
||||
return () => {
|
||||
setWebpageThumbnail(undefined);
|
||||
};
|
||||
}, [initialInfo]);
|
||||
const commentMediaInfo = webpageThumbnail || initialInfo;
|
||||
|
||||
const commentMediaInfo = useCommentMediaInfo(post);
|
||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||
|
||||
const isReply = parentCid;
|
||||
|
||||
const stateString = useStateString(post);
|
||||
|
||||
const handleUserAddressClick = useAuthorAddressClick();
|
||||
@@ -192,22 +176,9 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P
|
||||
};
|
||||
|
||||
const PostMediaContent = ({ post, link, t }: { post: any; link: string; t: any }) => {
|
||||
const initialInfo = getCommentMediaInfo(post);
|
||||
const [webpageThumbnail, setWebpageThumbnail] = useState<CommentMediaInfo | undefined>();
|
||||
const [showThumbnail, setShowThumbnail] = useState(true);
|
||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||
useEffect(() => {
|
||||
// some sites have CORS access, so the thumbnail can be fetched client-side, which is helpful if subplebbit.settings.fetchThumbnailUrls is false
|
||||
const loadThumbnail = async () => {
|
||||
if (initialInfo?.type === 'webpage' && !initialInfo.thumbnail) {
|
||||
const newMediaInfo = await fetchWebpageThumbnailIfNeeded(initialInfo);
|
||||
setWebpageThumbnail(newMediaInfo);
|
||||
}
|
||||
};
|
||||
loadThumbnail();
|
||||
}, [initialInfo]);
|
||||
|
||||
const commentMediaInfo = webpageThumbnail || initialInfo;
|
||||
const commentMediaInfo = useCommentMediaInfo(post);
|
||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||
|
||||
return (
|
||||
|
||||
@@ -45,7 +45,6 @@
|
||||
|
||||
.warning {
|
||||
font-size: 0.8em;
|
||||
color: red;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
|
||||
.warning {
|
||||
font-size: 0.8em;
|
||||
color: red;
|
||||
padding: 0 10px 10px 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
@@ -183,6 +183,18 @@ const AvatarSettings = () => {
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.avatarSettingInput}>
|
||||
<span className={styles.settingTitle}>{t('timestamp')}</span>
|
||||
<input
|
||||
type='text'
|
||||
placeholder='Timestamp'
|
||||
autoCorrect='off'
|
||||
autoComplete='off'
|
||||
spellCheck='false'
|
||||
value={timestamp}
|
||||
onChange={(e) => setTimestamp(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.pasteSignature}>
|
||||
<span className={styles.settingTitle}>{t('paste_signature')}</span>
|
||||
<input
|
||||
|
||||
@@ -41,6 +41,10 @@
|
||||
margin: 0 5px;
|
||||
}
|
||||
|
||||
.saved {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ const CryptoAddressSetting = () => {
|
||||
const account = useAccount();
|
||||
|
||||
const [cryptoState, setCryptoState] = useState({
|
||||
cryptoAddress: '',
|
||||
cryptoAddress: account?.author?.shortAddress.includes('.') ? account.author.shortAddress : '',
|
||||
checkingCryptoAddress: false,
|
||||
showResolvingMessage: true,
|
||||
resolveString: t('crypto_address_verification'),
|
||||
@@ -62,6 +62,12 @@ const CryptoAddressSetting = () => {
|
||||
if (!cryptoState.cryptoAddress || !cryptoState.cryptoAddress.includes('.')) {
|
||||
alert(t('enter_crypto_address'));
|
||||
return;
|
||||
} else if (cryptoState.cryptoAddress === account?.author?.address) {
|
||||
setSavedCryptoAddress(true);
|
||||
setTimeout(() => {
|
||||
setSavedCryptoAddress(false);
|
||||
}, 2000);
|
||||
return;
|
||||
} else if (resolvedAddress && resolvedAddress !== account?.signer?.address) {
|
||||
alert(t('crypto_address_not_yours'));
|
||||
return;
|
||||
@@ -110,7 +116,7 @@ const CryptoAddressSetting = () => {
|
||||
<input
|
||||
type='text'
|
||||
placeholder='address.eth/.sol'
|
||||
value={inputValue}
|
||||
value={cryptoState.cryptoAddress}
|
||||
onChange={(e) => {
|
||||
setInputValue(e.target.value);
|
||||
setCryptoState((prevState) => ({ ...prevState, cryptoAddress: e.target.value }));
|
||||
|
||||
@@ -12,7 +12,7 @@ interface SettingsProps {
|
||||
maticRpcRef?: RefObject<HTMLTextAreaElement>;
|
||||
avaxRpcRef?: RefObject<HTMLTextAreaElement>;
|
||||
plebbitRpcRef?: RefObject<HTMLInputElement>;
|
||||
nodeDataPathRef?: RefObject<HTMLInputElement>;
|
||||
plebbitDataPathRef?: RefObject<HTMLInputElement>;
|
||||
}
|
||||
|
||||
const IPFSGatewaysSettings = ({ ipfsGatewayUrlsRef, mediaIpfsGatewayUrlRef }: SettingsProps) => {
|
||||
@@ -119,21 +119,23 @@ const PlebbitRPCSettings = ({ plebbitRpcRef }: SettingsProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const NodeDataPathSettings = ({ nodeDataPathRef }: SettingsProps) => {
|
||||
const PlebbitDataPathSettings = ({ plebbitDataPathRef }: SettingsProps) => {
|
||||
const plebbitRpc = usePlebbitRpcSettings();
|
||||
const { plebbitRpcSettings } = plebbitRpc || {};
|
||||
const isConnectedToRpc = plebbitRpc?.state === 'succeeded';
|
||||
const path = plebbitRpcSettings?.plebbitOptions?.dataPath || '';
|
||||
const path = plebbitRpcSettings?.plebbitOptions?.plebbitDataPath || '';
|
||||
|
||||
return (
|
||||
<div className={styles.nodeDataPathSettings}>
|
||||
<div className={styles.plebbitDataPathSettings}>
|
||||
<div>
|
||||
<input type='text' defaultValue={path} disabled={!isConnectedToRpc} ref={nodeDataPathRef} />
|
||||
<input type='text' defaultValue={path} disabled={!isConnectedToRpc} ref={plebbitDataPathRef} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const isElectron = window.isElectron === true;
|
||||
|
||||
const PlebbitOptions = () => {
|
||||
const { t } = useTranslation();
|
||||
const account = useAccount();
|
||||
@@ -146,19 +148,50 @@ const PlebbitOptions = () => {
|
||||
const solRpcRef = useRef<HTMLTextAreaElement>(null);
|
||||
const maticRpcRef = useRef<HTMLTextAreaElement>(null);
|
||||
const avaxRpcRef = useRef<HTMLTextAreaElement>(null);
|
||||
const httpRoutersRef = useRef<HTMLTextAreaElement>(null);
|
||||
const plebbitRpcRef = useRef<HTMLInputElement>(null);
|
||||
const nodeDataPathRef = useRef<HTMLInputElement>(null);
|
||||
const plebbitDataPathRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleSave = async () => {
|
||||
const ipfsGatewayUrls = ipfsGatewayUrlsRef.current?.value.split('\n').map((url) => url.trim());
|
||||
const ipfsGatewayUrls = ipfsGatewayUrlsRef.current?.value
|
||||
.split('\n')
|
||||
.map((url) => url.trim())
|
||||
.filter((url) => url !== '');
|
||||
|
||||
const mediaIpfsGatewayUrl = mediaIpfsGatewayUrlRef.current?.value.trim();
|
||||
const pubsubHttpClientsOptions = pubsubProvidersRef.current?.value.split('\n').map((url) => url.trim());
|
||||
const ethRpcUrls = ethRpcRef.current?.value.split('\n').map((url) => url.trim());
|
||||
const solRpcUrls = solRpcRef.current?.value.split('\n').map((url) => url.trim());
|
||||
const maticRpcUrls = maticRpcRef.current?.value.split('\n').map((url) => url.trim());
|
||||
const avaxRpcUrls = avaxRpcRef.current?.value.split('\n').map((url) => url.trim());
|
||||
const plebbitRpcClientsOptions = plebbitRpcRef.current?.value.trim();
|
||||
const dataPath = nodeDataPathRef.current?.value.trim();
|
||||
|
||||
const pubsubHttpClientsOptions = pubsubProvidersRef.current?.value
|
||||
.split('\n')
|
||||
.map((url) => url.trim())
|
||||
.filter((url) => url !== '');
|
||||
|
||||
const ethRpcUrls = ethRpcRef.current?.value
|
||||
.split('\n')
|
||||
.map((url) => url.trim())
|
||||
.filter((url) => url !== '');
|
||||
|
||||
const solRpcUrls = solRpcRef.current?.value
|
||||
.split('\n')
|
||||
.map((url) => url.trim())
|
||||
.filter((url) => url !== '');
|
||||
|
||||
const maticRpcUrls = maticRpcRef.current?.value
|
||||
.split('\n')
|
||||
.map((url) => url.trim())
|
||||
.filter((url) => url !== '');
|
||||
|
||||
const avaxRpcUrls = avaxRpcRef.current?.value
|
||||
.split('\n')
|
||||
.map((url) => url.trim())
|
||||
.filter((url) => url !== '');
|
||||
|
||||
const httpRoutersOptions = httpRoutersRef.current?.value
|
||||
.split('\n')
|
||||
.map((url) => url.trim())
|
||||
.filter((url) => url !== '');
|
||||
|
||||
const plebbitRpcClientsOptions = plebbitRpcRef.current?.value.trim() ? [plebbitRpcRef.current.value.trim()] : undefined;
|
||||
const dataPath = plebbitDataPathRef.current?.value.trim() || undefined;
|
||||
|
||||
const chainProviders = {
|
||||
eth: {
|
||||
@@ -188,11 +221,12 @@ const PlebbitOptions = () => {
|
||||
ipfsGatewayUrls,
|
||||
pubsubHttpClientsOptions,
|
||||
chainProviders,
|
||||
httpRoutersOptions,
|
||||
plebbitRpcClientsOptions,
|
||||
dataPath,
|
||||
},
|
||||
});
|
||||
alert('Options saved.');
|
||||
alert('Options saved, reloading...');
|
||||
window.location.reload();
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
@@ -230,17 +264,19 @@ const PlebbitOptions = () => {
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.category}>
|
||||
<span className={styles.categoryTitle}>node RPC:</span>
|
||||
<span className={styles.categoryTitle}>plebbit RPC:</span>
|
||||
<span className={styles.categorySettings}>
|
||||
<PlebbitRPCSettings plebbitRpcRef={plebbitRpcRef} />
|
||||
</span>
|
||||
</div>
|
||||
<div className={styles.category}>
|
||||
<span className={styles.categoryTitle}>node data path:</span>
|
||||
<span className={styles.categorySettings}>
|
||||
<NodeDataPathSettings nodeDataPathRef={nodeDataPathRef} />
|
||||
</span>
|
||||
</div>
|
||||
{isElectron && (
|
||||
<div className={styles.category}>
|
||||
<span className={styles.categoryTitle}>plebbit data path:</span>
|
||||
<span className={styles.categorySettings}>
|
||||
<PlebbitDataPathSettings plebbitDataPathRef={plebbitDataPathRef} />
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { Comment } from '@plebbit/plebbit-react-hooks';
|
||||
import { getCommentMediaInfo, fetchWebpageThumbnailIfNeeded } from '../lib/utils/media-utils';
|
||||
import { isPendingPostView, isPostPageView } from '../lib/utils/view-utils';
|
||||
|
||||
export const useCommentMediaInfo = (comment: Comment) => {
|
||||
const location = useLocation();
|
||||
const params = useParams();
|
||||
const isInPostPageView = isPostPageView(location.pathname, params);
|
||||
const isInPendingPostView = isPendingPostView(location.pathname, params);
|
||||
// some sites have CORS access, so the thumbnail can be fetched client-side, which is helpful if subplebbit.settings.fetchThumbnailUrls is false
|
||||
const initialCommentMediaInfo = useMemo(() => getCommentMediaInfo(comment), [comment]);
|
||||
const [commentMediaInfo, setCommentMediaInfo] = useState(initialCommentMediaInfo);
|
||||
|
||||
const fetchThumbnail = useCallback(async () => {
|
||||
if (!isInPostPageView && !isInPendingPostView) {
|
||||
return; // don't fetch in feed view, it displaces the posts
|
||||
}
|
||||
if (initialCommentMediaInfo?.type === 'webpage' && !initialCommentMediaInfo.thumbnail) {
|
||||
const newMediaInfo = await fetchWebpageThumbnailIfNeeded(initialCommentMediaInfo);
|
||||
|
||||
// Fetch the dimensions of the thumbnail
|
||||
if (newMediaInfo.thumbnail) {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
setCommentMediaInfo({
|
||||
...newMediaInfo,
|
||||
thumbnailWidth: img.width,
|
||||
thumbnailHeight: img.height,
|
||||
});
|
||||
};
|
||||
img.src = newMediaInfo.thumbnail;
|
||||
} else {
|
||||
setCommentMediaInfo(newMediaInfo);
|
||||
}
|
||||
}
|
||||
}, [initialCommentMediaInfo, isInPostPageView, isInPendingPostView]);
|
||||
useEffect(() => {
|
||||
fetchThumbnail();
|
||||
}, [fetchThumbnail]);
|
||||
|
||||
return commentMediaInfo;
|
||||
};
|
||||
@@ -75,8 +75,13 @@ const useFetchGifFirstFrame = (url: string | undefined) => {
|
||||
try {
|
||||
const cachedFrame = await getCachedGifFrame(url);
|
||||
if (cachedFrame) {
|
||||
if (isActive) setFrameUrl(cachedFrame);
|
||||
return;
|
||||
try {
|
||||
const response = await fetch(cachedFrame);
|
||||
if (response.ok) {
|
||||
if (isActive) setFrameUrl(cachedFrame);
|
||||
return;
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const blob = typeof url === 'string' ? await parseGif(await fetchImage(url)) : await parseGif(await readImage(url as File));
|
||||
|
||||
@@ -10,6 +10,8 @@ export interface CommentMediaInfo {
|
||||
url: string;
|
||||
type: string;
|
||||
thumbnail?: string;
|
||||
thumbnailWidth?: number;
|
||||
thumbnailHeight?: number;
|
||||
patternThumbnailUrl?: string;
|
||||
post?: Comment;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user