Merge pull request #618 from plebbit/development

Development
This commit is contained in:
Tom (plebeius.eth)
2024-12-14 21:27:55 +01:00
committed by GitHub
48 changed files with 260 additions and 185 deletions
+1 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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 -1
View File
@@ -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 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",
+1 -1
View File
@@ -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",
+1 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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",
+1 -1
View File
@@ -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 -1
View File
@@ -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 -1
View File
@@ -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ử",
+1 -1
View File
@@ -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": "加密货币钱包",
+4 -16
View File
@@ -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();
+76 -46
View File
@@ -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}`}>
+4 -25
View File
@@ -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);
+6 -35
View File
@@ -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>
);
};
+44
View File
@@ -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;
};
+7 -2
View File
@@ -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));
+2
View File
@@ -10,6 +10,8 @@ export interface CommentMediaInfo {
url: string;
type: string;
thumbnail?: string;
thumbnailWidth?: number;
thumbnailHeight?: number;
patternThumbnailUrl?: string;
post?: Comment;
}