mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
@@ -136,7 +136,7 @@
|
|||||||
"collapse_all_settings": "طي جميع الإعدادات",
|
"collapse_all_settings": "طي جميع الإعدادات",
|
||||||
"refresh": "تحديث",
|
"refresh": "تحديث",
|
||||||
"no_board_selected": "لم يتم تحديد أية منتديات",
|
"no_board_selected": "لم يتم تحديد أية منتديات",
|
||||||
"none": "بلا",
|
"none": "لا شيء",
|
||||||
"not_subscribed_to_any_board": "لم تشترك في أي منتديات بعد.",
|
"not_subscribed_to_any_board": "لم تشترك في أي منتديات بعد.",
|
||||||
"board_mods": "مشرفو المنتدى",
|
"board_mods": "مشرفو المنتدى",
|
||||||
"this_post_was_removed": "تمت إزالة هذا المنشور",
|
"this_post_was_removed": "تمت إزالة هذا المنشور",
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "مشاركة واحدة بهذا المعرف",
|
"1_post_by_this_id": "مشاركة واحدة بهذا المعرف",
|
||||||
"x_posts_by_this_id": "{{number}} منشور من هذا المعرف",
|
"x_posts_by_this_id": "{{number}} منشور من هذا المعرف",
|
||||||
"ban_expires_at": "محظور من p/{{address}} حتى {{timestamp}}",
|
"ban_expires_at": "محظور من p/{{address}} حتى {{timestamp}}",
|
||||||
"user_banned": "تم حظر المستخدم لهذا المنشور"
|
"user_banned": "تم حظر المستخدم لهذا المنشور",
|
||||||
|
"loading_comments": "تحميل التعليقات",
|
||||||
|
"replies": "الردود",
|
||||||
|
"links": "الروابط",
|
||||||
|
"avatar": "الصورة الرمزية",
|
||||||
|
"token_address_whitelist": "عنوان الرمز (فقط على الـ <1>قائمة البيضاء</1>)",
|
||||||
|
"token_id": "تعريف الرمز"
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
"collapse_all_settings": "সমস্ত সেটিংস সঙ্কুচিত করুন",
|
"collapse_all_settings": "সমস্ত সেটিংস সঙ্কুচিত করুন",
|
||||||
"refresh": "পুনরায় লোড করুন",
|
"refresh": "পুনরায় লোড করুন",
|
||||||
"no_board_selected": "কোনো ফোরাম নির্বাচিত হয়নি",
|
"no_board_selected": "কোনো ফোরাম নির্বাচিত হয়নি",
|
||||||
"none": "নাই",
|
"none": "কিছু না",
|
||||||
"not_subscribed_to_any_board": "আপনি এখনো কোনো ফোরামে সদস্য হননি।",
|
"not_subscribed_to_any_board": "আপনি এখনো কোনো ফোরামে সদস্য হননি।",
|
||||||
"board_mods": "বোর্ড মডস",
|
"board_mods": "বোর্ড মডস",
|
||||||
"this_post_was_removed": "এই পোস্টটি সরানো হয়েছে",
|
"this_post_was_removed": "এই পোস্টটি সরানো হয়েছে",
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "এই আইডি দ্বারা 1 পোস্ট",
|
"1_post_by_this_id": "এই আইডি দ্বারা 1 পোস্ট",
|
||||||
"x_posts_by_this_id": "এই আইডি থেকে {{number}} পোস্ট",
|
"x_posts_by_this_id": "এই আইডি থেকে {{number}} পোস্ট",
|
||||||
"ban_expires_at": "p/{{address}} থেকে {{timestamp}} পর্যন্ত নিষিদ্ধ",
|
"ban_expires_at": "p/{{address}} থেকে {{timestamp}} পর্যন্ত নিষিদ্ধ",
|
||||||
"user_banned": "এই পোস্টের জন্য ব্যবহারকারী নিষিদ্ধ হয়েছে"
|
"user_banned": "এই পোস্টের জন্য ব্যবহারকারী নিষিদ্ধ হয়েছে",
|
||||||
|
"loading_comments": "মন্তব্য লোড হচ্ছে",
|
||||||
|
"replies": "উত্তর",
|
||||||
|
"links": "লিঙ্কগুলি",
|
||||||
|
"avatar": "অবতার",
|
||||||
|
"token_address_whitelist": "টোকেন ঠিকানা (<1>সাফল্যবান</1> কেবল)",
|
||||||
|
"token_id": "টোকেন আইডি"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 příspěvek s tímto ID",
|
"1_post_by_this_id": "1 příspěvek s tímto ID",
|
||||||
"x_posts_by_this_id": "{{number}} příspěvků z tohoto ID",
|
"x_posts_by_this_id": "{{number}} příspěvků z tohoto ID",
|
||||||
"ban_expires_at": "Zakázán z p/{{address}} do {{timestamp}}",
|
"ban_expires_at": "Zakázán z p/{{address}} do {{timestamp}}",
|
||||||
"user_banned": "Uživatel byl zablokován za tento příspěvek"
|
"user_banned": "Uživatel byl zablokován za tento příspěvek",
|
||||||
|
"loading_comments": "Načítání komentářů",
|
||||||
|
"replies": "Odpovědi",
|
||||||
|
"links": "Odkazy",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Tokenová adresa (<1>jen bílý seznam</1>)",
|
||||||
|
"token_id": "Token ID"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 post med dette ID",
|
"1_post_by_this_id": "1 post med dette ID",
|
||||||
"x_posts_by_this_id": "{{number}} indlæg fra denne ID",
|
"x_posts_by_this_id": "{{number}} indlæg fra denne ID",
|
||||||
"ban_expires_at": "Udelukket fra p/{{address}} indtil {{timestamp}}",
|
"ban_expires_at": "Udelukket fra p/{{address}} indtil {{timestamp}}",
|
||||||
"user_banned": "Brugeren blev blokeret for dette indlæg"
|
"user_banned": "Brugeren blev blokeret for dette indlæg",
|
||||||
|
"loading_comments": "Indlæser kommentarer",
|
||||||
|
"replies": "Svar",
|
||||||
|
"links": "Links",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Tokenadresse (<1>kun hvidliste</1>)",
|
||||||
|
"token_id": "Token ID"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 Beitrag mit dieser ID",
|
"1_post_by_this_id": "1 Beitrag mit dieser ID",
|
||||||
"x_posts_by_this_id": "{{number}} Beiträge von dieser ID",
|
"x_posts_by_this_id": "{{number}} Beiträge von dieser ID",
|
||||||
"ban_expires_at": "Gesperrt von p/{{address}} bis {{timestamp}}",
|
"ban_expires_at": "Gesperrt von p/{{address}} bis {{timestamp}}",
|
||||||
"user_banned": "Benutzer wurde für diesen Beitrag gesperrt"
|
"user_banned": "Benutzer wurde für diesen Beitrag gesperrt",
|
||||||
|
"loading_comments": "Lade Kommentare",
|
||||||
|
"replies": "Antworten",
|
||||||
|
"links": "Links",
|
||||||
|
"avatar": "Avatar",
|
||||||
|
"token_address_whitelist": "Tokenadresse (<1>nur Weiße Liste</1>)",
|
||||||
|
"token_id": "Token-ID"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 ανάρτηση με αυτό το ID",
|
"1_post_by_this_id": "1 ανάρτηση με αυτό το ID",
|
||||||
"x_posts_by_this_id": "{{number}} αναρτήσεις από αυτό το ID",
|
"x_posts_by_this_id": "{{number}} αναρτήσεις από αυτό το ID",
|
||||||
"ban_expires_at": "Αποκλείστηκε από p/{{address}} έως {{timestamp}}",
|
"ban_expires_at": "Αποκλείστηκε από p/{{address}} έως {{timestamp}}",
|
||||||
"user_banned": "Ο χρήστης αποκλείστηκε για αυτή τη δημοσίευση"
|
"user_banned": "Ο χρήστης αποκλείστηκε για αυτή τη δημοσίευση",
|
||||||
|
"loading_comments": "Φόρτωση σχολίων",
|
||||||
|
"replies": "Απαντήσεις",
|
||||||
|
"links": "Σύνδεσμοι",
|
||||||
|
"avatar": "εικόνα προφίλ",
|
||||||
|
"token_address_whitelist": "Διεύθυνση του token (<1>μόνο λευκή λίστα</1>)",
|
||||||
|
"token_id": "Token ID"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 post by this ID",
|
"1_post_by_this_id": "1 post by this ID",
|
||||||
"x_posts_by_this_id": "{{number}} posts from this ID",
|
"x_posts_by_this_id": "{{number}} posts from this ID",
|
||||||
"ban_expires_at": "Banned from p/{{address}} until {{timestamp}}",
|
"ban_expires_at": "Banned from p/{{address}} until {{timestamp}}",
|
||||||
"user_banned": "User was banned for this post"
|
"user_banned": "User was banned for this post",
|
||||||
|
"loading_comments": "Loading comments",
|
||||||
|
"replies": "replies",
|
||||||
|
"links": "links",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Token address (<1>whitelist</1> only)",
|
||||||
|
"token_id": "token id"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 publicación con este ID",
|
"1_post_by_this_id": "1 publicación con este ID",
|
||||||
"x_posts_by_this_id": "{{number}} publicaciones desde este ID",
|
"x_posts_by_this_id": "{{number}} publicaciones desde este ID",
|
||||||
"ban_expires_at": "Baneado de p/{{address}} hasta {{timestamp}}",
|
"ban_expires_at": "Baneado de p/{{address}} hasta {{timestamp}}",
|
||||||
"user_banned": "El usuario fue baneado por esta publicación"
|
"user_banned": "El usuario fue baneado por esta publicación",
|
||||||
|
"loading_comments": "Cargando comentarios",
|
||||||
|
"replies": "respuestas",
|
||||||
|
"links": "enlaces",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Dirección del token (<1>solo lista blanca</1>)",
|
||||||
|
"token_id": "ID de token"
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
"collapse_all_settings": "تايید همه تنظیمات",
|
"collapse_all_settings": "تايید همه تنظیمات",
|
||||||
"refresh": "تازهسازی",
|
"refresh": "تازهسازی",
|
||||||
"no_board_selected": "هیچ تالاری انتخاب نشده است",
|
"no_board_selected": "هیچ تالاری انتخاب نشده است",
|
||||||
"none": "هیچکدام",
|
"none": "هیچکدام",
|
||||||
"not_subscribed_to_any_board": "هنوز به هیچ تالاری مشترک نشدهاید.",
|
"not_subscribed_to_any_board": "هنوز به هیچ تالاری مشترک نشدهاید.",
|
||||||
"board_mods": "مدیران تالار",
|
"board_mods": "مدیران تالار",
|
||||||
"this_post_was_removed": "این پست حذف شد",
|
"this_post_was_removed": "این پست حذف شد",
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 پست با این شناسه",
|
"1_post_by_this_id": "1 پست با این شناسه",
|
||||||
"x_posts_by_this_id": "{{number}} پست از این شناسه",
|
"x_posts_by_this_id": "{{number}} پست از این شناسه",
|
||||||
"ban_expires_at": "مسدود از p/{{address}} تا {{timestamp}}",
|
"ban_expires_at": "مسدود از p/{{address}} تا {{timestamp}}",
|
||||||
"user_banned": "این کاربر برای این پست مسدود شد"
|
"user_banned": "این کاربر برای این پست مسدود شد",
|
||||||
|
"loading_comments": "بارگذاری نظرات",
|
||||||
|
"replies": "پاسخها",
|
||||||
|
"links": "لینکها",
|
||||||
|
"avatar": "آواتار",
|
||||||
|
"token_address_whitelist": "آدرس توکن (<1>فقط سفیدپوشان</1>)",
|
||||||
|
"token_id": "شناسه توکن"
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
"collapse_all_settings": "Sulje kaikki asetukset",
|
"collapse_all_settings": "Sulje kaikki asetukset",
|
||||||
"refresh": "Päivitä",
|
"refresh": "Päivitä",
|
||||||
"no_board_selected": "ei foorumeja valittu",
|
"no_board_selected": "ei foorumeja valittu",
|
||||||
"none": "ei mikään",
|
"none": "ei mitään",
|
||||||
"not_subscribed_to_any_board": "Et ole vielä tilannut mitään foorumeita.",
|
"not_subscribed_to_any_board": "Et ole vielä tilannut mitään foorumeita.",
|
||||||
"board_mods": "Hallituksen Mods",
|
"board_mods": "Hallituksen Mods",
|
||||||
"this_post_was_removed": "tämä viesti on poistettu",
|
"this_post_was_removed": "tämä viesti on poistettu",
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 viesti tällä tunnuksella",
|
"1_post_by_this_id": "1 viesti tällä tunnuksella",
|
||||||
"x_posts_by_this_id": "{{number}} viestiä tältä tunnukselta",
|
"x_posts_by_this_id": "{{number}} viestiä tältä tunnukselta",
|
||||||
"ban_expires_at": "Porttikielto p/{{address}} asti {{timestamp}}",
|
"ban_expires_at": "Porttikielto p/{{address}} asti {{timestamp}}",
|
||||||
"user_banned": "Käyttäjä on estetty tästä viestistä"
|
"user_banned": "Käyttäjä on estetty tästä viestistä",
|
||||||
|
"loading_comments": "Ladataan kommentteja",
|
||||||
|
"replies": "vastaukset",
|
||||||
|
"links": "linkit",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Token-osoite (<1>vain valkoinen lista</1>)",
|
||||||
|
"token_id": "Token-id"
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
"collapse_all_settings": "I-collapse ang lahat ng mga setting",
|
"collapse_all_settings": "I-collapse ang lahat ng mga setting",
|
||||||
"refresh": "I-refresh",
|
"refresh": "I-refresh",
|
||||||
"no_board_selected": "walang mga board na napiling",
|
"no_board_selected": "walang mga board na napiling",
|
||||||
"none": "wala",
|
"none": "walang anuman",
|
||||||
"not_subscribed_to_any_board": "Hindi ka pa naka-subscribe sa anumang mga board.",
|
"not_subscribed_to_any_board": "Hindi ka pa naka-subscribe sa anumang mga board.",
|
||||||
"board_mods": "Board Mods",
|
"board_mods": "Board Mods",
|
||||||
"this_post_was_removed": "tinanggal ang post na ito",
|
"this_post_was_removed": "tinanggal ang post na ito",
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 post sa pamamagitan ng ID na ito",
|
"1_post_by_this_id": "1 post sa pamamagitan ng ID na ito",
|
||||||
"x_posts_by_this_id": "{{number}} post mula sa ID na ito",
|
"x_posts_by_this_id": "{{number}} post mula sa ID na ito",
|
||||||
"ban_expires_at": "Bawal sa p/{{address}} hanggang {{timestamp}}",
|
"ban_expires_at": "Bawal sa p/{{address}} hanggang {{timestamp}}",
|
||||||
"user_banned": "Banal ang user para sa post na ito"
|
"user_banned": "Banal ang user para sa post na ito",
|
||||||
|
"loading_comments": "Naglo-load ng mga komento",
|
||||||
|
"replies": "mga tugon",
|
||||||
|
"links": "mga link",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Address ng token (<1>whitelist</1> lang)",
|
||||||
|
"token_id": "Token ID"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 publication avec cet identifiant",
|
"1_post_by_this_id": "1 publication avec cet identifiant",
|
||||||
"x_posts_by_this_id": "{{number}} publications depuis cet identifiant",
|
"x_posts_by_this_id": "{{number}} publications depuis cet identifiant",
|
||||||
"ban_expires_at": "Banni de p/{{address}} jusqu'au {{timestamp}}",
|
"ban_expires_at": "Banni de p/{{address}} jusqu'au {{timestamp}}",
|
||||||
"user_banned": "L'utilisateur a été banni pour ce post"
|
"user_banned": "L'utilisateur a été banni pour ce post",
|
||||||
|
"loading_comments": "Chargement des commentaires",
|
||||||
|
"replies": "réponses",
|
||||||
|
"links": "liens",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Adresse du token (<1>liste blanche</1> seulement)",
|
||||||
|
"token_id": "Identifiant de jeton"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "פוסט אחד עם המזהה הזה",
|
"1_post_by_this_id": "פוסט אחד עם המזהה הזה",
|
||||||
"x_posts_by_this_id": "{{number}} פוסטים מהמזהה הזה",
|
"x_posts_by_this_id": "{{number}} פוסטים מהמזהה הזה",
|
||||||
"ban_expires_at": "נחסם מ-p/{{address}} עד {{timestamp}}",
|
"ban_expires_at": "נחסם מ-p/{{address}} עד {{timestamp}}",
|
||||||
"user_banned": "המשתמש נחסם עבור הפוסט הזה"
|
"user_banned": "המשתמש נחסם עבור הפוסט הזה",
|
||||||
|
"loading_comments": "טוען תגובות",
|
||||||
|
"replies": "תגובות",
|
||||||
|
"links": "קישורים",
|
||||||
|
"avatar": "אוואטר",
|
||||||
|
"token_address_whitelist": "כתובת הטוקן (<1>רק רשימה לבנה</1>)",
|
||||||
|
"token_id": "מזהה טוקן"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "इस आईडी द्वारा 1 पोस्ट",
|
"1_post_by_this_id": "इस आईडी द्वारा 1 पोस्ट",
|
||||||
"x_posts_by_this_id": "इस आईडी से {{number}} पोस्ट",
|
"x_posts_by_this_id": "इस आईडी से {{number}} पोस्ट",
|
||||||
"ban_expires_at": "p/{{address}} से {{timestamp}} तक पर प्रतिबंधित",
|
"ban_expires_at": "p/{{address}} से {{timestamp}} तक पर प्रतिबंधित",
|
||||||
"user_banned": "इस पोस्ट के लिए उपयोगकर्ता पर प्रतिबंध लगा दिया गया था"
|
"user_banned": "इस पोस्ट के लिए उपयोगकर्ता पर प्रतिबंध लगा दिया गया था",
|
||||||
|
"loading_comments": "टिप्पणियाँ लोड हो रही हैं",
|
||||||
|
"replies": "जवाब",
|
||||||
|
"links": "लिंक",
|
||||||
|
"avatar": "अवतार",
|
||||||
|
"token_address_whitelist": "टोकन पता (<1>केवल सफेद सूची</1>)",
|
||||||
|
"token_id": "टोकन आईडी"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 bejegyzés ezzel az azonosítóval",
|
"1_post_by_this_id": "1 bejegyzés ezzel az azonosítóval",
|
||||||
"x_posts_by_this_id": "{{number}} bejegyzés erről az azonosítóról",
|
"x_posts_by_this_id": "{{number}} bejegyzés erről az azonosítóról",
|
||||||
"ban_expires_at": "Tiltva p/{{address}}-tól {{timestamp}}-ig",
|
"ban_expires_at": "Tiltva p/{{address}}-tól {{timestamp}}-ig",
|
||||||
"user_banned": "A felhasználót letiltották ennek a posztnak a kapcsán"
|
"user_banned": "A felhasználót letiltották ennek a posztnak a kapcsán",
|
||||||
|
"loading_comments": "Megjegyzések betöltése",
|
||||||
|
"replies": "válaszok",
|
||||||
|
"links": "linkek",
|
||||||
|
"avatar": "avatár",
|
||||||
|
"token_address_whitelist": "Token cím (<1>csak fehérlista</1>)",
|
||||||
|
"token_id": "Token azonosító"
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
"collapse_all_settings": "Ciutkan Semua Pengaturan",
|
"collapse_all_settings": "Ciutkan Semua Pengaturan",
|
||||||
"refresh": "Segarkan",
|
"refresh": "Segarkan",
|
||||||
"no_board_selected": "tidak ada papan pesan yang dipilih",
|
"no_board_selected": "tidak ada papan pesan yang dipilih",
|
||||||
"none": "tak ada",
|
"none": "tidak ada",
|
||||||
"not_subscribed_to_any_board": "Anda belum berlangganan ke papan pesan mana pun.",
|
"not_subscribed_to_any_board": "Anda belum berlangganan ke papan pesan mana pun.",
|
||||||
"board_mods": "Papan Pesan Mods",
|
"board_mods": "Papan Pesan Mods",
|
||||||
"this_post_was_removed": "posting ini telah dihapus",
|
"this_post_was_removed": "posting ini telah dihapus",
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 pos dengan ID ini",
|
"1_post_by_this_id": "1 pos dengan ID ini",
|
||||||
"x_posts_by_this_id": "{{number}} pos dari ID ini",
|
"x_posts_by_this_id": "{{number}} pos dari ID ini",
|
||||||
"ban_expires_at": "Dilarang dari p/{{address}} sampai {{timestamp}}",
|
"ban_expires_at": "Dilarang dari p/{{address}} sampai {{timestamp}}",
|
||||||
"user_banned": "Pengguna diblokir untuk pos ini"
|
"user_banned": "Pengguna diblokir untuk pos ini",
|
||||||
|
"loading_comments": "Memuat komentar",
|
||||||
|
"replies": "balasan",
|
||||||
|
"links": "tautan",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Alamat token (<1>hanya daftar putih</1>)",
|
||||||
|
"token_id": "ID token"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 post con questo ID",
|
"1_post_by_this_id": "1 post con questo ID",
|
||||||
"x_posts_by_this_id": "{{number}} post da questo ID",
|
"x_posts_by_this_id": "{{number}} post da questo ID",
|
||||||
"ban_expires_at": "Bannato da p/{{address}} fino al {{timestamp}}",
|
"ban_expires_at": "Bannato da p/{{address}} fino al {{timestamp}}",
|
||||||
"user_banned": "L'utente è stato bannato per questo post"
|
"user_banned": "L'utente è stato bannato per questo post",
|
||||||
|
"loading_comments": "Caricamento commenti",
|
||||||
|
"replies": "commenti",
|
||||||
|
"links": "link",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Indirizzo del token (solo se sulla <1>whitelist</1>)",
|
||||||
|
"token_id": "token id"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "このIDで1件の投稿",
|
"1_post_by_this_id": "このIDで1件の投稿",
|
||||||
"x_posts_by_this_id": "このIDからの{{number}} 件の投稿",
|
"x_posts_by_this_id": "このIDからの{{number}} 件の投稿",
|
||||||
"ban_expires_at": "p/{{address}}から{{timestamp}}まで禁止",
|
"ban_expires_at": "p/{{address}}から{{timestamp}}まで禁止",
|
||||||
"user_banned": "この投稿のためにユーザーが禁止されました"
|
"user_banned": "この投稿のためにユーザーが禁止されました",
|
||||||
|
"loading_comments": "コメントを読み込み中",
|
||||||
|
"replies": "返信",
|
||||||
|
"links": "リンク",
|
||||||
|
"avatar": "アバター",
|
||||||
|
"token_address_whitelist": "トークンアドレス(<1>ホワイトリスト</1>のみ)",
|
||||||
|
"token_id": "トークンID"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "이 ID로 게시된 1개의 글",
|
"1_post_by_this_id": "이 ID로 게시된 1개의 글",
|
||||||
"x_posts_by_this_id": "이 ID로부터의 {{number}}개의 글",
|
"x_posts_by_this_id": "이 ID로부터의 {{number}}개의 글",
|
||||||
"ban_expires_at": "p/{{address}}에서 {{timestamp}}까지 금지됨",
|
"ban_expires_at": "p/{{address}}에서 {{timestamp}}까지 금지됨",
|
||||||
"user_banned": "이 게시물로 인해 사용자가 차단되었습니다"
|
"user_banned": "이 게시물로 인해 사용자가 차단되었습니다",
|
||||||
|
"loading_comments": "댓글 로드 중",
|
||||||
|
"replies": "답글",
|
||||||
|
"links": "링크",
|
||||||
|
"avatar": "아바타",
|
||||||
|
"token_address_whitelist": "토큰 주소 (<1>화이트리스트</1>만)",
|
||||||
|
"token_id": "토큰 ID"
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
"collapse_all_settings": "सर्व सेटिंग्ज संक्षेपित",
|
"collapse_all_settings": "सर्व सेटिंग्ज संक्षेपित",
|
||||||
"refresh": "ताजेतर करा",
|
"refresh": "ताजेतर करा",
|
||||||
"no_board_selected": "कोणतेही संदेश फलक निवडले नाहीत",
|
"no_board_selected": "कोणतेही संदेश फलक निवडले नाहीत",
|
||||||
"none": "काहीही नाही",
|
"none": "काही नाही",
|
||||||
"not_subscribed_to_any_board": "तुम्ही कोणत्याही संदेश फलकांवर अद्याप सदस्यता घेतली नाही.",
|
"not_subscribed_to_any_board": "तुम्ही कोणत्याही संदेश फलकांवर अद्याप सदस्यता घेतली नाही.",
|
||||||
"board_mods": "बोर्ड मॉड्स",
|
"board_mods": "बोर्ड मॉड्स",
|
||||||
"this_post_was_removed": "हा पोस्ट काढून टाकला गेला आहे",
|
"this_post_was_removed": "हा पोस्ट काढून टाकला गेला आहे",
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "या ID वर 1 पोस्ट",
|
"1_post_by_this_id": "या ID वर 1 पोस्ट",
|
||||||
"x_posts_by_this_id": "या ID पासून {{number}} पोस्ट",
|
"x_posts_by_this_id": "या ID पासून {{number}} पोस्ट",
|
||||||
"ban_expires_at": "p/{{address}} ते {{timestamp}} पर्यंत प्रतिबंधित",
|
"ban_expires_at": "p/{{address}} ते {{timestamp}} पर्यंत प्रतिबंधित",
|
||||||
"user_banned": "या पोस्टसाठी वापरकर्ता ब्लॉक केला गेला होता"
|
"user_banned": "या पोस्टसाठी वापरकर्ता ब्लॉक केला गेला होता",
|
||||||
|
"loading_comments": "टिप्पणी लोड होत आहे",
|
||||||
|
"replies": "उत्तर",
|
||||||
|
"links": "लिंक",
|
||||||
|
"avatar": "अवतार",
|
||||||
|
"token_address_whitelist": "टोकन पत्ता (<1>केवळ सफेद सूची</1>)",
|
||||||
|
"token_id": "टोकन आयडी"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 bericht met dit ID",
|
"1_post_by_this_id": "1 bericht met dit ID",
|
||||||
"x_posts_by_this_id": "{{number}} berichten met dit ID",
|
"x_posts_by_this_id": "{{number}} berichten met dit ID",
|
||||||
"ban_expires_at": "Verbannen van p/{{address}} tot {{timestamp}}",
|
"ban_expires_at": "Verbannen van p/{{address}} tot {{timestamp}}",
|
||||||
"user_banned": "Gebruiker is verbannen voor deze post"
|
"user_banned": "Gebruiker is verbannen voor deze post",
|
||||||
|
"loading_comments": "Reacties laden",
|
||||||
|
"replies": "reacties",
|
||||||
|
"links": "links",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Tokenadres (<1>alleen whitelist</1>)",
|
||||||
|
"token_id": "Token-ID"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 post med denne ID-en",
|
"1_post_by_this_id": "1 post med denne ID-en",
|
||||||
"x_posts_by_this_id": "{{number}} poster med denne ID-en",
|
"x_posts_by_this_id": "{{number}} poster med denne ID-en",
|
||||||
"ban_expires_at": "Utestengt fra p/{{address}} til {{timestamp}}",
|
"ban_expires_at": "Utestengt fra p/{{address}} til {{timestamp}}",
|
||||||
"user_banned": "Brukeren ble utestengt for dette innlegget"
|
"user_banned": "Brukeren ble utestengt for dette innlegget",
|
||||||
|
"loading_comments": "Laster inn kommentarer",
|
||||||
|
"replies": "svar",
|
||||||
|
"links": "lenker",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Tokenadresse (<1>kun hvitelisten</1>)",
|
||||||
|
"token_id": "Token ID"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 post z tym identyfikatorem",
|
"1_post_by_this_id": "1 post z tym identyfikatorem",
|
||||||
"x_posts_by_this_id": "{{number}} postów z tego identyfikatora",
|
"x_posts_by_this_id": "{{number}} postów z tego identyfikatora",
|
||||||
"ban_expires_at": "Zablokowany na p/{{address}} do {{timestamp}}",
|
"ban_expires_at": "Zablokowany na p/{{address}} do {{timestamp}}",
|
||||||
"user_banned": "Użytkownik został zablokowany za ten post"
|
"user_banned": "Użytkownik został zablokowany za ten post",
|
||||||
|
"loading_comments": "Ładowanie komentarzy",
|
||||||
|
"replies": "odpowiedzi",
|
||||||
|
"links": "linki",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Adres tokenu (<1>tylko biała lista</1>)",
|
||||||
|
"token_id": "Identyfikator tokena"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 post com este ID",
|
"1_post_by_this_id": "1 post com este ID",
|
||||||
"x_posts_by_this_id": "{{number}} posts com este ID",
|
"x_posts_by_this_id": "{{number}} posts com este ID",
|
||||||
"ban_expires_at": "Banido de p/{{address}} até {{timestamp}}",
|
"ban_expires_at": "Banido de p/{{address}} até {{timestamp}}",
|
||||||
"user_banned": "O usuário foi banido por este post"
|
"user_banned": "O usuário foi banido por este post",
|
||||||
|
"loading_comments": "Carregando comentários",
|
||||||
|
"replies": "respostas",
|
||||||
|
"links": "links",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Endereço do token (<1>apenas lista branca</1>)",
|
||||||
|
"token_id": "ID do token"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 post cu acest ID",
|
"1_post_by_this_id": "1 post cu acest ID",
|
||||||
"x_posts_by_this_id": "{{number}} postări cu acest ID",
|
"x_posts_by_this_id": "{{number}} postări cu acest ID",
|
||||||
"ban_expires_at": "Interzis de la p/{{address}} până la {{timestamp}}",
|
"ban_expires_at": "Interzis de la p/{{address}} până la {{timestamp}}",
|
||||||
"user_banned": "Utilizatorul a fost interzis pentru acest mesaj"
|
"user_banned": "Utilizatorul a fost interzis pentru acest mesaj",
|
||||||
|
"loading_comments": "Se încarcă comentariile",
|
||||||
|
"replies": "răspunsuri",
|
||||||
|
"links": "linkuri",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Adresa tokenului (<1>doar lista albă</1>)",
|
||||||
|
"token_id": "ID token"
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
"collapse_all_settings": "Свернуть все настройки",
|
"collapse_all_settings": "Свернуть все настройки",
|
||||||
"refresh": "Обновить",
|
"refresh": "Обновить",
|
||||||
"no_board_selected": "не выбрано ни одного форума",
|
"no_board_selected": "не выбрано ни одного форума",
|
||||||
"none": "нет",
|
"none": "ничего",
|
||||||
"not_subscribed_to_any_board": "Вы еще не подписаны ни на один форум.",
|
"not_subscribed_to_any_board": "Вы еще не подписаны ни на один форум.",
|
||||||
"board_mods": "Модераторы форума",
|
"board_mods": "Модераторы форума",
|
||||||
"this_post_was_removed": "этот пост был удален",
|
"this_post_was_removed": "этот пост был удален",
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 сообщение с этим идентификатором",
|
"1_post_by_this_id": "1 сообщение с этим идентификатором",
|
||||||
"x_posts_by_this_id": "{{number}} сообщений с этого идентификатора",
|
"x_posts_by_this_id": "{{number}} сообщений с этого идентификатора",
|
||||||
"ban_expires_at": "Забанен на p/{{address}} до {{timestamp}}",
|
"ban_expires_at": "Забанен на p/{{address}} до {{timestamp}}",
|
||||||
"user_banned": "Пользователь был заблокирован за этот пост"
|
"user_banned": "Пользователь был заблокирован за этот пост",
|
||||||
|
"loading_comments": "Загрузка комментариев",
|
||||||
|
"replies": "ответы",
|
||||||
|
"links": "ссылки",
|
||||||
|
"avatar": "аватар",
|
||||||
|
"token_address_whitelist": "Адрес токена (<1>только белый список</1>)",
|
||||||
|
"token_id": "Идентификатор токена"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 postim me këtë ID",
|
"1_post_by_this_id": "1 postim me këtë ID",
|
||||||
"x_posts_by_this_id": "{{number}} postime nga ky ID",
|
"x_posts_by_this_id": "{{number}} postime nga ky ID",
|
||||||
"ban_expires_at": "I ndaluar nga p/{{address}} deri më {{timestamp}}",
|
"ban_expires_at": "I ndaluar nga p/{{address}} deri më {{timestamp}}",
|
||||||
"user_banned": "Përdoruesi u ndalua për këtë postim"
|
"user_banned": "Përdoruesi u ndalua për këtë postim",
|
||||||
|
"loading_comments": "Duke u ngarkuar komentet",
|
||||||
|
"replies": "përgjigje",
|
||||||
|
"links": "lidhje",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Adresa e tokenit (<1>vetëm lista e bardhë</1>)",
|
||||||
|
"token_id": "ID e tokenit"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 inlägg med detta ID",
|
"1_post_by_this_id": "1 inlägg med detta ID",
|
||||||
"x_posts_by_this_id": "{{number}} inlägg från detta ID",
|
"x_posts_by_this_id": "{{number}} inlägg från detta ID",
|
||||||
"ban_expires_at": "Bannad från p/{{address}} till {{timestamp}}",
|
"ban_expires_at": "Bannad från p/{{address}} till {{timestamp}}",
|
||||||
"user_banned": "Användaren blev blockerad för detta inlägg"
|
"user_banned": "Användaren blev blockerad för detta inlägg",
|
||||||
|
"loading_comments": "Laddar kommentarer",
|
||||||
|
"replies": "svar",
|
||||||
|
"links": "länkar",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Tokenadress (<1>endast vitlista</1>)",
|
||||||
|
"token_id": "Token ID"
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
"collapse_all_settings": "అన్ని సెట్టింగ్లను సంక్షిప్తము చేయండి",
|
"collapse_all_settings": "అన్ని సెట్టింగ్లను సంక్షిప్తము చేయండి",
|
||||||
"refresh": "రీఫ్రెష్",
|
"refresh": "రీఫ్రెష్",
|
||||||
"no_board_selected": "ఏటీ ఎలాంటి సందేశ బోర్డులు ఎంచుకోలేదు",
|
"no_board_selected": "ఏటీ ఎలాంటి సందేశ బోర్డులు ఎంచుకోలేదు",
|
||||||
"none": "ఏమీ లేదు",
|
"none": "ఎవరూ లేరు",
|
||||||
"not_subscribed_to_any_board": "మీరు ఇంకా ఏకైక సందేశ బోర్డులకు చేరలేదు.",
|
"not_subscribed_to_any_board": "మీరు ఇంకా ఏకైక సందేశ బోర్డులకు చేరలేదు.",
|
||||||
"board_mods": "సందేశ బోర్డుల మాడ్స్",
|
"board_mods": "సందేశ బోర్డుల మాడ్స్",
|
||||||
"this_post_was_removed": "ఈ పోస్టు తొలగించబడింది",
|
"this_post_was_removed": "ఈ పోస్టు తొలగించబడింది",
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "ఈ ID ద్వారా 1 పోస్టు",
|
"1_post_by_this_id": "ఈ ID ద్వారా 1 పోస్టు",
|
||||||
"x_posts_by_this_id": "ఈ ID నుండి {{number}} పోస్టు",
|
"x_posts_by_this_id": "ఈ ID నుండి {{number}} పోస్టు",
|
||||||
"ban_expires_at": "p/{{address}} నుండి {{timestamp}} వరకు నిషేధించబడింది",
|
"ban_expires_at": "p/{{address}} నుండి {{timestamp}} వరకు నిషేధించబడింది",
|
||||||
"user_banned": "ఈ పోస్టు కోసం వాడు బ్యాన్ చేయబడింది"
|
"user_banned": "ఈ పోస్టు కోసం వాడు బ్యాన్ చేయబడింది",
|
||||||
|
"loading_comments": "వ్యాఖ్యలను లోడ్ అవుతోంది",
|
||||||
|
"replies": "సమాధానాలు",
|
||||||
|
"links": "లింక్లు",
|
||||||
|
"avatar": "అవతార్",
|
||||||
|
"token_address_whitelist": "టోకెన్ చిరునామా (<1>కేవలం వెలుపల్లి</1>)",
|
||||||
|
"token_id": "టోకెన్ ఐడి"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 โพสต์โดย ID นี้",
|
"1_post_by_this_id": "1 โพสต์โดย ID นี้",
|
||||||
"x_posts_by_this_id": "{{number}} โพสต์จาก ID นี้",
|
"x_posts_by_this_id": "{{number}} โพสต์จาก ID นี้",
|
||||||
"ban_expires_at": "ถูกห้ามใช้งานจาก p/{{address}} จนถึง {{timestamp}}",
|
"ban_expires_at": "ถูกห้ามใช้งานจาก p/{{address}} จนถึง {{timestamp}}",
|
||||||
"user_banned": "ผู้ใช้ถูกห้ามใช้งานสำหรับโพสต์นี้"
|
"user_banned": "ผู้ใช้ถูกห้ามใช้งานสำหรับโพสต์นี้",
|
||||||
|
"loading_comments": "กำลังโหลดความคิดเห็น",
|
||||||
|
"replies": "คำตอบ",
|
||||||
|
"links": "ลิงก์",
|
||||||
|
"avatar": "อวตาร์",
|
||||||
|
"token_address_whitelist": "ที่อยู่โทเค็น (<1>รายชื่อที่ได้รับอนุญาตเท่านั้น</1>)",
|
||||||
|
"token_id": "รหัสโทเค็น"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "Bu ID ile 1 gönderi",
|
"1_post_by_this_id": "Bu ID ile 1 gönderi",
|
||||||
"x_posts_by_this_id": "Bu ID ile {{number}} gönderi",
|
"x_posts_by_this_id": "Bu ID ile {{number}} gönderi",
|
||||||
"ban_expires_at": "p/{{address}} adresinden {{timestamp}} tarihine kadar yasaklandı",
|
"ban_expires_at": "p/{{address}} adresinden {{timestamp}} tarihine kadar yasaklandı",
|
||||||
"user_banned": "Kullanıcı bu gönderi için yasaklandı"
|
"user_banned": "Kullanıcı bu gönderi için yasaklandı",
|
||||||
|
"loading_comments": "Yorumlar yükleniyor",
|
||||||
|
"replies": "cevaplar",
|
||||||
|
"links": "bağlantılar",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Token adresi (<1>yalnızca beyaz liste</1>)",
|
||||||
|
"token_id": "Token kimliği"
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
"collapse_all_settings": "Згорнути всі налаштування",
|
"collapse_all_settings": "Згорнути всі налаштування",
|
||||||
"refresh": "Оновити",
|
"refresh": "Оновити",
|
||||||
"no_board_selected": "не обрано жодного форуму",
|
"no_board_selected": "не обрано жодного форуму",
|
||||||
"none": "немає",
|
"none": "ніхто",
|
||||||
"not_subscribed_to_any_board": "Ви ще не підписалися на жоден форум.",
|
"not_subscribed_to_any_board": "Ви ще не підписалися на жоден форум.",
|
||||||
"board_mods": "Дошка Моди",
|
"board_mods": "Дошка Моди",
|
||||||
"this_post_was_removed": "цей пост було видалено",
|
"this_post_was_removed": "цей пост було видалено",
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 пост з цим ідентифікатором",
|
"1_post_by_this_id": "1 пост з цим ідентифікатором",
|
||||||
"x_posts_by_this_id": "{{number}} пост з цього ідентифікатора",
|
"x_posts_by_this_id": "{{number}} пост з цього ідентифікатора",
|
||||||
"ban_expires_at": "Заборонений з p/{{address}} до {{timestamp}}",
|
"ban_expires_at": "Заборонений з p/{{address}} до {{timestamp}}",
|
||||||
"user_banned": "Користувач був заблокований за цей пост"
|
"user_banned": "Користувач був заблокований за цей пост",
|
||||||
|
"loading_comments": "Завантаження коментарів",
|
||||||
|
"replies": "відповіді",
|
||||||
|
"links": "посилання",
|
||||||
|
"avatar": "аватар",
|
||||||
|
"token_address_whitelist": "Адреса токена (<1>тільки білий список</1>)",
|
||||||
|
"token_id": "Ідентифікатор токена"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "اس آئی ڈی پر 1 پوسٹ",
|
"1_post_by_this_id": "اس آئی ڈی پر 1 پوسٹ",
|
||||||
"x_posts_by_this_id": "اس آئی ڈی سے {{number}} پوسٹ",
|
"x_posts_by_this_id": "اس آئی ڈی سے {{number}} پوسٹ",
|
||||||
"ban_expires_at": "p/{{address}} سے {{timestamp}} تک ممنوع",
|
"ban_expires_at": "p/{{address}} سے {{timestamp}} تک ممنوع",
|
||||||
"user_banned": "اس پوسٹ کے لئے صارف کو پابندی عائد کردی گئی تھی"
|
"user_banned": "اس پوسٹ کے لئے صارف کو پابندی عائد کردی گئی تھی",
|
||||||
|
"loading_comments": "تبصرے لوڈ ہو رہے ہیں",
|
||||||
|
"replies": "جوابات",
|
||||||
|
"links": "لنکس",
|
||||||
|
"avatar": "اوتار",
|
||||||
|
"token_address_whitelist": "ٹوکن کا پتا (<1>صرف سفید فہرست</1>)",
|
||||||
|
"token_id": "ٹوکن شناخت"
|
||||||
}
|
}
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "1 bài đăng bởi ID này",
|
"1_post_by_this_id": "1 bài đăng bởi ID này",
|
||||||
"x_posts_by_this_id": "{{number}} bài đăng từ ID này",
|
"x_posts_by_this_id": "{{number}} bài đăng từ ID này",
|
||||||
"ban_expires_at": "Bị cấm từ p/{{address}} cho đến {{timestamp}}",
|
"ban_expires_at": "Bị cấm từ p/{{address}} cho đến {{timestamp}}",
|
||||||
"user_banned": "Người dùng đã bị cấm vì bài đăng này"
|
"user_banned": "Người dùng đã bị cấm vì bài đăng này",
|
||||||
|
"loading_comments": "Đang tải bình luận",
|
||||||
|
"replies": "trả lời",
|
||||||
|
"links": "liên kết",
|
||||||
|
"avatar": "avatar",
|
||||||
|
"token_address_whitelist": "Địa chỉ token (<1>chỉ danh sách trắng</1>)",
|
||||||
|
"token_id": "ID token"
|
||||||
}
|
}
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
"collapse_all_settings": "折叠所有设置",
|
"collapse_all_settings": "折叠所有设置",
|
||||||
"refresh": "刷新",
|
"refresh": "刷新",
|
||||||
"no_board_selected": "没有选择任何论坛",
|
"no_board_selected": "没有选择任何论坛",
|
||||||
"none": "无",
|
"none": "没有",
|
||||||
"not_subscribed_to_any_board": "您尚未订阅任何论坛。",
|
"not_subscribed_to_any_board": "您尚未订阅任何论坛。",
|
||||||
"board_mods": "董事會版主",
|
"board_mods": "董事會版主",
|
||||||
"this_post_was_removed": "此帖已被删除",
|
"this_post_was_removed": "此帖已被删除",
|
||||||
@@ -210,5 +210,11 @@
|
|||||||
"1_post_by_this_id": "此ID的1篇帖子",
|
"1_post_by_this_id": "此ID的1篇帖子",
|
||||||
"x_posts_by_this_id": "此ID的{{number}}篇帖子",
|
"x_posts_by_this_id": "此ID的{{number}}篇帖子",
|
||||||
"ban_expires_at": "被禁止从p/{{address}}到{{timestamp}}",
|
"ban_expires_at": "被禁止从p/{{address}}到{{timestamp}}",
|
||||||
"user_banned": "用户因此帖子被禁止"
|
"user_banned": "用户因此帖子被禁止",
|
||||||
|
"loading_comments": "正在加载评论",
|
||||||
|
"replies": "回复",
|
||||||
|
"links": "链接",
|
||||||
|
"avatar": "头像",
|
||||||
|
"token_address_whitelist": "代币地址(<1>仅限白名单</1>)",
|
||||||
|
"token_id": "令牌ID"
|
||||||
}
|
}
|
||||||
@@ -10,6 +10,7 @@ import CatalogFilters from '../../views/catalog/catalog-filters/';
|
|||||||
import styles from './board-buttons.module.css';
|
import styles from './board-buttons.module.css';
|
||||||
import Tooltip from '../tooltip';
|
import Tooltip from '../tooltip';
|
||||||
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
|
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
interface BoardButtonsProps {
|
interface BoardButtonsProps {
|
||||||
address?: string | undefined;
|
address?: string | undefined;
|
||||||
@@ -223,6 +224,7 @@ export const MobileBoardButtons = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PostPageStats = () => {
|
const PostPageStats = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isInDescriptionView = isDescriptionView(location.pathname, params);
|
const isInDescriptionView = isDescriptionView(location.pathname, params);
|
||||||
@@ -232,11 +234,14 @@ const PostPageStats = () => {
|
|||||||
const { closed, pinned, replyCount } = comment || {};
|
const { closed, pinned, replyCount } = comment || {};
|
||||||
const linkCount = useCountLinksInReplies(comment);
|
const linkCount = useCountLinksInReplies(comment);
|
||||||
|
|
||||||
|
const displayReplyCount = replyCount !== undefined ? replyCount.toString() : '?';
|
||||||
|
const replyCountTooltip = replyCount !== undefined ? _.capitalize(t('replies')) : t('loading');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{(pinned || isInDescriptionView || isInRulesView) && 'Sticky / '}
|
{(pinned || isInDescriptionView || isInRulesView) && `${_.capitalize(t('sticky'))} / `}
|
||||||
{(closed || isInDescriptionView || isInRulesView) && 'Closed / '}
|
{(closed || isInDescriptionView || isInRulesView) && `${_.capitalize(t('closed'))} / `}
|
||||||
<Tooltip children={replyCount?.toString()} content='Replies' /> / <Tooltip children={linkCount?.toString()} content='Links' />
|
<Tooltip children={displayReplyCount} content={replyCountTooltip} /> / <Tooltip children={linkCount?.toString()} content={_.capitalize(t('links'))} />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.offlineIcon {
|
.offlineIcon {
|
||||||
background-image: url('/public/assets/icons/offline.png');
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
width: 16px;
|
width: 16px;
|
||||||
@@ -39,10 +38,6 @@
|
|||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
}
|
}
|
||||||
|
|
||||||
.redOfflineIcon {
|
|
||||||
background-image: url('/public/assets/icons/offline-red.png') !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.boardTitle {
|
.boardTitle {
|
||||||
padding-top: 40px;
|
padding-top: 40px;
|
||||||
|
|||||||
@@ -34,8 +34,7 @@ const BoardHeader = () => {
|
|||||||
const title = isInAllView ? multisubMetadata?.title || 'all' : isInSubscriptionsView ? 'Subscriptions' : subplebbit?.title;
|
const title = isInAllView ? multisubMetadata?.title || 'all' : isInSubscriptionsView ? 'Subscriptions' : subplebbit?.title;
|
||||||
const subtitle = isInAllView ? 'p/all' : isInSubscriptionsView ? 'p/subscriptions' : `p/${address}`;
|
const subtitle = isInAllView ? 'p/all' : isInSubscriptionsView ? 'p/subscriptions' : `p/${address}`;
|
||||||
|
|
||||||
const { isOffline, isDefinitelyOffline, offlineTitle } = useIsSubplebbitOffline(subplebbit);
|
const { isOffline, isOnlineStatusLoading, offlineIconClass, offlineTitle } = useIsSubplebbitOffline(subplebbit);
|
||||||
const iconClass = `${styles.offlineIcon} ${isDefinitelyOffline ? styles.redOfflineIcon : ''}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
@@ -46,7 +45,7 @@ const BoardHeader = () => {
|
|||||||
)}
|
)}
|
||||||
<div className={styles.boardTitle}>
|
<div className={styles.boardTitle}>
|
||||||
{title || `p/${shortAddress || subplebbitAddress}`}
|
{title || `p/${shortAddress || subplebbitAddress}`}
|
||||||
{isOffline && <span className={`${styles.offlineIcon} ${iconClass}`} title={offlineTitle} />}
|
{(isOffline || isOnlineStatusLoading) && <span className={`${styles.offlineIcon} ${offlineIconClass}`} title={offlineTitle} />}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.boardSubtitle}>{subtitle}</div>
|
<div className={styles.boardSubtitle}>{subtitle}</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { createPortal } from 'react-dom';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||||
import { Comment, useComment } from '@plebbit/plebbit-react-hooks';
|
import { Comment, useComment } from '@plebbit/plebbit-react-hooks';
|
||||||
import { useFloating, offset, shift, size, autoUpdate, Placement } from '@floating-ui/react';
|
import { useFloating, offset, size, autoUpdate, Placement } from '@floating-ui/react';
|
||||||
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
||||||
import { getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
import { getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||||
import { isAllView } from '../../lib/utils/view-utils';
|
import { isAllView } from '../../lib/utils/view-utils';
|
||||||
@@ -141,7 +141,6 @@ const CatalogPost = ({ post }: { post: Comment }) => {
|
|||||||
open: showPortal,
|
open: showPortal,
|
||||||
placement: placementRef.current,
|
placement: placementRef.current,
|
||||||
middleware: [
|
middleware: [
|
||||||
shift({ padding: 10 }),
|
|
||||||
offset({ mainAxis: 5 }),
|
offset({ mainAxis: 5 }),
|
||||||
size({
|
size({
|
||||||
apply({ elements }) {
|
apply({ elements }) {
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ const EditMenu = ({ isAccountMod, isAccountCommentAuthor, isCommentAuthorMod, po
|
|||||||
deleted: isAccountCommentAuthor ? deleted : undefined,
|
deleted: isAccountCommentAuthor ? deleted : undefined,
|
||||||
locked: isAccountMod ? locked : undefined,
|
locked: isAccountMod ? locked : undefined,
|
||||||
pinned: isAccountMod ? pinned : undefined,
|
pinned: isAccountMod ? pinned : undefined,
|
||||||
|
reason,
|
||||||
removed: isAccountMod ? removed : undefined,
|
removed: isAccountMod ? removed : undefined,
|
||||||
spoiler,
|
spoiler,
|
||||||
subplebbitAddress,
|
subplebbitAddress,
|
||||||
@@ -82,8 +83,7 @@ const EditMenu = ({ isAccountMod, isAccountCommentAuthor, isCommentAuthorMod, po
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
const onReason = (e: React.ChangeEvent<HTMLInputElement>) =>
|
const onReason = (e: React.ChangeEvent<HTMLInputElement>) => setPublishCommentEditOptions((state) => ({ ...state, reason: e.target.value }));
|
||||||
setPublishCommentEditOptions((state) => ({ ...state, reason: e.target.value ? e.target.value : undefined }));
|
|
||||||
|
|
||||||
const { refs, floatingStyles, context } = useFloating({
|
const { refs, floatingStyles, context } = useFloating({
|
||||||
placement: 'bottom-start',
|
placement: 'bottom-start',
|
||||||
@@ -206,7 +206,7 @@ const EditMenu = ({ isAccountMod, isAccountCommentAuthor, isCommentAuthorMod, po
|
|||||||
)}
|
)}
|
||||||
<div className={`${styles.menuItem} ${styles.menuReason}`}>
|
<div className={`${styles.menuItem} ${styles.menuReason}`}>
|
||||||
{_.capitalize(t('reason'))}? ({t('optional')})
|
{_.capitalize(t('reason'))}? ({t('optional')})
|
||||||
<input type='text' onChange={onReason} defaultValue={reason ?? ''} size={14} />
|
<input type='text' onChange={onReason} value={publishCommentEditOptions.reason} size={14} />
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.bottom}>
|
<div className={styles.bottom}>
|
||||||
<button className={isMobile ? 'button' : ''} onClick={_publishCommentEdit}>
|
<button className={isMobile ? 'button' : ''} onClick={_publishCommentEdit}>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||||
import { Comment, useAccount, useComment, useEditedComment } from '@plebbit/plebbit-react-hooks';
|
import { Comment, useAccount, useAuthorAvatar, useComment, useEditedComment } from '@plebbit/plebbit-react-hooks';
|
||||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||||
import styles from '../../views/post/post.module.css';
|
import styles from '../../views/post/post.module.css';
|
||||||
import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../lib/utils/media-utils';
|
import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../lib/utils/media-utils';
|
||||||
@@ -54,6 +54,7 @@ const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }:
|
|||||||
const stateString = useStateString(post);
|
const stateString = useStateString(post);
|
||||||
const isReply = parentCid;
|
const isReply = parentCid;
|
||||||
const { showOmittedReplies } = useShowOmittedReplies();
|
const { showOmittedReplies } = useShowOmittedReplies();
|
||||||
|
const { imageUrl: avatarImageUrl } = useAuthorAvatar({ author });
|
||||||
|
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -99,6 +100,7 @@ const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }:
|
|||||||
</span>
|
</span>
|
||||||
{!(isDescription || isRules) && (
|
{!(isDescription || isRules) && (
|
||||||
<>
|
<>
|
||||||
|
{isReply && author?.avatar && <span className={styles.authorAvatar} style={{ backgroundImage: `url(${avatarImageUrl})` }} />}
|
||||||
(u/
|
(u/
|
||||||
<Tooltip
|
<Tooltip
|
||||||
children={
|
children={
|
||||||
@@ -410,6 +412,11 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
|
|||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
|
{replyCount === undefined && (
|
||||||
|
<span className={styles.loadingString}>
|
||||||
|
<LoadingEllipsis string={t('loading_comments')} />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{!isHidden &&
|
{!isHidden &&
|
||||||
!(pinned && !isInPostPageView && !showOmittedReplies[cid]) &&
|
!(pinned && !isInPostPageView && !showOmittedReplies[cid]) &&
|
||||||
!isInPendingPostView &&
|
!isInPendingPostView &&
|
||||||
|
|||||||
@@ -277,7 +277,7 @@ const PostForm = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.postFormDesktop}>
|
<div className={styles.postFormDesktop}>
|
||||||
{!(isInAllView || isInSubscriptionsView) && showForm && isOffline && <div className={styles.offlineBoard}>{offlineTitle}</div>}
|
{!(isInAllView || isInSubscriptionsView) && showForm && (isOffline || isOffline) && <div className={styles.offlineBoard}>{offlineTitle}</div>}
|
||||||
{isThreadClosed ? (
|
{isThreadClosed ? (
|
||||||
<div className={styles.closed}>
|
<div className={styles.closed}>
|
||||||
{t('thread_closed')}
|
{t('thread_closed')}
|
||||||
@@ -297,7 +297,7 @@ const PostForm = () => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.postFormMobile}>
|
<div className={styles.postFormMobile}>
|
||||||
{!(isInAllView || isInSubscriptionsView) && showForm && isOffline && <div className={styles.offlineBoard}>{offlineTitle}</div>}
|
{!(isInAllView || isInSubscriptionsView) && showForm && (isOffline || isOffline) && <div className={styles.offlineBoard}>{offlineTitle}</div>}
|
||||||
{isThreadClosed ? (
|
{isThreadClosed ? (
|
||||||
<div className={styles.closed}>
|
<div className={styles.closed}>
|
||||||
{t('thread_closed')}
|
{t('thread_closed')}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||||
import { Comment, useAccount, useComment, useEditedComment } from '@plebbit/plebbit-react-hooks';
|
import { Comment, useAccount, useAuthorAvatar, useComment, useEditedComment } from '@plebbit/plebbit-react-hooks';
|
||||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||||
import styles from '../../views/post/post.module.css';
|
import styles from '../../views/post/post.module.css';
|
||||||
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
||||||
@@ -29,6 +29,7 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P
|
|||||||
const { address, shortAddress } = author || {};
|
const { address, shortAddress } = author || {};
|
||||||
const displayName = author?.displayName?.trim();
|
const displayName = author?.displayName?.trim();
|
||||||
const authorRole = roles?.[address]?.role;
|
const authorRole = roles?.[address]?.role;
|
||||||
|
const { imageUrl: avatarImageUrl } = useAuthorAvatar({ author });
|
||||||
|
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@@ -73,6 +74,7 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P
|
|||||||
</span>
|
</span>
|
||||||
{!(isDescription || isRules) && (
|
{!(isDescription || isRules) && (
|
||||||
<>
|
<>
|
||||||
|
{isReply && author?.avatar && <span className={styles.authorAvatar} style={{ backgroundImage: `url(${avatarImageUrl})` }} />}
|
||||||
(u/
|
(u/
|
||||||
<Tooltip
|
<Tooltip
|
||||||
children={
|
children={
|
||||||
@@ -336,6 +338,11 @@ const PostMobile = ({ openReplyModal, post, roles, showAllReplies, showReplies =
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
{replyCount === undefined && (
|
||||||
|
<span className={styles.loadingString}>
|
||||||
|
<LoadingEllipsis string={t('loading_comments')} />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{!(pinned && !isInPostView) &&
|
{!(pinned && !isInPostView) &&
|
||||||
!isInPendingPostView &&
|
!isInPendingPostView &&
|
||||||
!isDescription &&
|
!isDescription &&
|
||||||
|
|||||||
@@ -130,6 +130,7 @@ const AccountSettings = () => {
|
|||||||
await importAccount(fileContent);
|
await importAccount(fileContent);
|
||||||
setSwitchToLastAccount(true);
|
setSwitchToLastAccount(true);
|
||||||
alert(`Imported ${newAccount.account?.name}`);
|
alert(`Imported ${newAccount.account?.name}`);
|
||||||
|
window.location.reload();
|
||||||
};
|
};
|
||||||
reader.readAsText(file);
|
reader.readAsText(file);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
.avatar {
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
border: 1px solid #aaa;
|
||||||
|
background-color: var(--avatar-background-color, white);
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarPreview {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar img {
|
||||||
|
width: 70px;
|
||||||
|
height: 70px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emptyAvatar {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
text-align: center;
|
||||||
|
height: 100%;
|
||||||
|
color: var(--avatar-text-color);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarSettingsForm {
|
||||||
|
padding: 10px 0 15px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarSettingsForm input {
|
||||||
|
width: 200px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarSettingInput input {
|
||||||
|
margin-bottom: 10px;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarSettingInput a {
|
||||||
|
color: var(--link-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarSettingInput a {
|
||||||
|
color: var(--post-link-text-color);
|
||||||
|
text-decoration: var(--post-content-link-text-decoration);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarSettingInput a:hover {
|
||||||
|
color: var(--post-link-text-color-hover);
|
||||||
|
text-decoration: var(--post-content-link-text-decoration-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settingTitle {
|
||||||
|
font-style: italic;
|
||||||
|
text-transform: lowercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyMessage {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pasteSignature span {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pasteSignature button {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.state {
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-left: 10px;
|
||||||
|
max-width: 300px;
|
||||||
|
color: var(--post-mobile-abbr-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyMessage a {
|
||||||
|
color: var(--text-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyMessage a:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
@@ -0,0 +1,200 @@
|
|||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { setAccount, useAccount, useAuthorAvatar } from '@plebbit/plebbit-react-hooks';
|
||||||
|
import styles from './avatar-settings.module.css';
|
||||||
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
|
import LoadingEllipsis from '../../loading-ellipsis';
|
||||||
|
|
||||||
|
const AvatarPreview = ({ avatar }: any) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const account = useAccount();
|
||||||
|
let author = useMemo(() => ({ ...account?.author, avatar }), [account, avatar]);
|
||||||
|
|
||||||
|
const { imageUrl, state, error } = useAuthorAvatar({ author });
|
||||||
|
|
||||||
|
// if avatar already set, and user hasn't typed anything yet, preview already set author
|
||||||
|
if (account?.author?.avatar && !avatar?.chainTicker && !avatar?.address && !avatar?.id && !avatar?.signature) {
|
||||||
|
author = account.author;
|
||||||
|
}
|
||||||
|
|
||||||
|
// not enough data to preview yet
|
||||||
|
if (!author?.avatar?.address && !author?.avatar?.signature) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const stateText = state !== 'succeeded' ? <LoadingEllipsis string={state} /> : undefined;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className={styles.avatar}>
|
||||||
|
{imageUrl && state !== 'initializing' ? <img src={imageUrl} alt='' /> : <span className={styles.emptyAvatar}>{t('none')}</span>}
|
||||||
|
</div>
|
||||||
|
{state !== 'succeeded' && account?.author?.avatar && (
|
||||||
|
<div className={styles.state}>
|
||||||
|
{stateText} {error?.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const AvatarSettings = () => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const account = useAccount();
|
||||||
|
const authorAddress = account?.author?.address;
|
||||||
|
const [chainTicker, setChainTicker] = useState(account?.author?.avatar?.chainTicker);
|
||||||
|
const [tokenAddress, setTokenAddress] = useState(account?.author?.avatar?.address);
|
||||||
|
const [tokenId, setTokenId] = useState(account?.author?.avatar?.id);
|
||||||
|
const [timestamp, setTimestamp] = useState(account?.author?.avatar?.timestamp);
|
||||||
|
const [signature, setSignature] = useState(account?.author?.avatar?.signature?.signature);
|
||||||
|
|
||||||
|
const getNftMessageToSign = (authorAddress: string, timestamp: number, tokenAddress: string, tokenId: string) => {
|
||||||
|
let messageToSign: any = {};
|
||||||
|
// the property names must be in this order for the signature to match
|
||||||
|
// insert props one at a time otherwise babel/webpack will reorder
|
||||||
|
messageToSign.domainSeparator = 'plebbit-author-avatar';
|
||||||
|
messageToSign.authorAddress = authorAddress;
|
||||||
|
messageToSign.timestamp = timestamp;
|
||||||
|
messageToSign.tokenAddress = tokenAddress;
|
||||||
|
messageToSign.tokenId = String(tokenId); // must be a type string, not number
|
||||||
|
// use plain JSON so the user can read what he's signing
|
||||||
|
messageToSign = JSON.stringify(messageToSign);
|
||||||
|
return messageToSign;
|
||||||
|
};
|
||||||
|
|
||||||
|
const [hasCopied, setHasCopied] = useState(false);
|
||||||
|
|
||||||
|
const copyMessageToSign = () => {
|
||||||
|
if (!chainTicker) {
|
||||||
|
return alert(t('missing_chain_ticker'));
|
||||||
|
}
|
||||||
|
if (!tokenAddress) {
|
||||||
|
return alert(t('missing_token_address'));
|
||||||
|
}
|
||||||
|
if (!tokenId) {
|
||||||
|
return alert(t('missing_token_id'));
|
||||||
|
}
|
||||||
|
const newTimestamp = Math.floor(Date.now() / 1000);
|
||||||
|
const messageToSign = getNftMessageToSign(authorAddress, newTimestamp, tokenAddress, tokenId);
|
||||||
|
// update timestamp every time the user gets a new message to sign
|
||||||
|
setTimestamp(newTimestamp);
|
||||||
|
navigator.clipboard.writeText(messageToSign);
|
||||||
|
setHasCopied(true);
|
||||||
|
setTimeout(() => {
|
||||||
|
setHasCopied(false);
|
||||||
|
}, 2000);
|
||||||
|
};
|
||||||
|
|
||||||
|
// how to resolve and verify NFT signatures https://github.com/plebbit/plebbit-js/blob/master/docs/nft.md
|
||||||
|
const avatar = {
|
||||||
|
chainTicker: chainTicker?.toLowerCase() || account?.author?.avatar?.chainTicker,
|
||||||
|
timestamp,
|
||||||
|
address: tokenAddress || account?.author?.avatar?.address,
|
||||||
|
id: tokenId || account?.author?.avatar?.id,
|
||||||
|
signature: {
|
||||||
|
signature: signature || account?.author?.avatar?.signature?.signature,
|
||||||
|
type: 'eip191',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const save = () => {
|
||||||
|
if (!chainTicker) {
|
||||||
|
return alert(t('missing_chain_ticker'));
|
||||||
|
}
|
||||||
|
if (!tokenAddress) {
|
||||||
|
return alert(t('missing_token_address'));
|
||||||
|
}
|
||||||
|
if (!tokenId) {
|
||||||
|
return alert(t('missing_token_id'));
|
||||||
|
}
|
||||||
|
if (!signature) {
|
||||||
|
return alert(t('missing_signature'));
|
||||||
|
}
|
||||||
|
setAccount({ ...account, author: { ...account?.author, avatar } });
|
||||||
|
alert(`saved`);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.avatarSettings}>
|
||||||
|
<AvatarPreview avatar={avatar} />
|
||||||
|
<div className={styles.avatarSettingsForm}>
|
||||||
|
<div className={styles.avatarSettingInput}>
|
||||||
|
<span className={styles.settingTitle}>{t('chain_ticker')}</span>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
placeholder='eth/sol/avax'
|
||||||
|
autoCorrect='off'
|
||||||
|
autoComplete='off'
|
||||||
|
spellCheck='false'
|
||||||
|
defaultValue={account?.author?.avatar?.chainTicker}
|
||||||
|
onChange={(e) => setChainTicker(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.avatarSettingInput}>
|
||||||
|
<span className={styles.settingTitle}>
|
||||||
|
<Trans
|
||||||
|
i18nKey='token_address_whitelist'
|
||||||
|
shouldUnescape={true}
|
||||||
|
components={{
|
||||||
|
1: (
|
||||||
|
<Link
|
||||||
|
to='https://github.com/plebbit/plebbit-react-hooks/blob/557cc3f40b5933a00553ed9c0bc310d2cd7a3b52/src/hooks/authors/author-avatars.ts#L133'
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
placeholder='0x...'
|
||||||
|
autoCorrect='off'
|
||||||
|
autoComplete='off'
|
||||||
|
spellCheck='false'
|
||||||
|
defaultValue={account?.author?.avatar?.address}
|
||||||
|
onChange={(e) => setTokenAddress(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.avatarSettingInput}>
|
||||||
|
<span className={styles.settingTitle}>{t('token_id')}</span>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
placeholder='Token ID'
|
||||||
|
autoCorrect='off'
|
||||||
|
autoComplete='off'
|
||||||
|
spellCheck='false'
|
||||||
|
defaultValue={account?.author?.avatar?.id}
|
||||||
|
onChange={(e) => setTokenId(e.target.value)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.copyMessage}>
|
||||||
|
<Trans
|
||||||
|
i18nKey='copy_message_etherscan'
|
||||||
|
values={{ copy: hasCopied ? t('copied') : t('copy') }}
|
||||||
|
components={{
|
||||||
|
1: <button onClick={copyMessageToSign} />,
|
||||||
|
// eslint-disable-next-line
|
||||||
|
2: <a href='https://etherscan.io/verifiedSignatures' target='_blank' rel='noopener noreferrer' />,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles.pasteSignature}>
|
||||||
|
<span className={styles.settingTitle}>{t('paste_signature')}</span>
|
||||||
|
<input
|
||||||
|
type='text'
|
||||||
|
placeholder='0x...'
|
||||||
|
autoCorrect='off'
|
||||||
|
autoComplete='off'
|
||||||
|
spellCheck='false'
|
||||||
|
defaultValue={account?.author?.avatar?.signature?.signature}
|
||||||
|
onChange={(e) => setSignature(e.target.value)}
|
||||||
|
/>
|
||||||
|
<button onClick={save}>{t('save')}</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AvatarSettings;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './avatar-settings';
|
||||||
@@ -18,14 +18,17 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ipfsGatewaysSettings textarea {
|
.ipfsGatewaysSettings textarea {
|
||||||
height: 70px;
|
min-height: 80px;
|
||||||
|
height: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pubsubProvidersSettings textarea {
|
.pubsubProvidersSettings textarea {
|
||||||
|
min-height: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blockchainProvidersSettings textarea {
|
.blockchainProvidersSettings textarea {
|
||||||
|
min-height: 50px;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -193,6 +193,7 @@ const PlebbitOptions = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
alert('Options saved.');
|
alert('Options saved.');
|
||||||
|
window.location.reload();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
alert('Error saving options: ' + e.message);
|
alert('Error saving options: ' + e.message);
|
||||||
|
|||||||
@@ -22,6 +22,10 @@
|
|||||||
padding: 2px 4px 3px;
|
padding: 2px 4px 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settingsModal input[type="text"]:focus, .settingsModal textarea:focus {
|
||||||
|
border: var(--reply-modal-field-input-border-focus, revert);
|
||||||
|
}
|
||||||
|
|
||||||
.settingsModal {
|
.settingsModal {
|
||||||
top: 25px;
|
top: 25px;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@@ -42,6 +46,10 @@
|
|||||||
border-left: var(--settings-modal-border-left);
|
border-left: var(--settings-modal-border-left);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settingsModal textarea {
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
.settingsModal select, .settingsModal button, .settingsModal textarea, .settingsModal input[type='text'] {
|
.settingsModal select, .settingsModal button, .settingsModal textarea, .settingsModal input[type='text'] {
|
||||||
filter: var(--filter80);
|
filter: var(--filter80);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import styles from './settings-modal.module.css';
|
import styles from './settings-modal.module.css';
|
||||||
import AccountSettings from './account-settings';
|
import AccountSettings from './account-settings';
|
||||||
|
import AvatarSettings from './avatar-settings';
|
||||||
import BlockedAddressesSetting from './blocked-addresses-setting';
|
import BlockedAddressesSetting from './blocked-addresses-setting';
|
||||||
import CryptoAddressSetting from './crypto-address-setting';
|
import CryptoAddressSetting from './crypto-address-setting';
|
||||||
import CryptoWalletsSetting from './crypto-wallets-setting';
|
import CryptoWalletsSetting from './crypto-wallets-setting';
|
||||||
@@ -19,6 +20,7 @@ const SettingsModal = () => {
|
|||||||
|
|
||||||
const [showInterfaceSettings, setShowInterfaceSettings] = useState(false);
|
const [showInterfaceSettings, setShowInterfaceSettings] = useState(false);
|
||||||
const [showAccountSettings, setShowAccountSettings] = useState(false);
|
const [showAccountSettings, setShowAccountSettings] = useState(false);
|
||||||
|
const [showAvatarSettings, setShowAvatarSettings] = useState(false);
|
||||||
const [showCryptoAddressSetting, setShowCryptoAddressSetting] = useState(false);
|
const [showCryptoAddressSetting, setShowCryptoAddressSetting] = useState(false);
|
||||||
const [showCryptoWalletSettings, setShowCryptoWalletSettings] = useState(false);
|
const [showCryptoWalletSettings, setShowCryptoWalletSettings] = useState(false);
|
||||||
const [showBlockedAddressesSetting, setShowBlockedAddressesSetting] = useState(false);
|
const [showBlockedAddressesSetting, setShowBlockedAddressesSetting] = useState(false);
|
||||||
@@ -30,6 +32,7 @@ const SettingsModal = () => {
|
|||||||
setExpandAll(newExpandState);
|
setExpandAll(newExpandState);
|
||||||
setShowInterfaceSettings(newExpandState);
|
setShowInterfaceSettings(newExpandState);
|
||||||
setShowAccountSettings(newExpandState);
|
setShowAccountSettings(newExpandState);
|
||||||
|
setShowAvatarSettings(newExpandState);
|
||||||
setShowCryptoAddressSetting(newExpandState);
|
setShowCryptoAddressSetting(newExpandState);
|
||||||
setShowCryptoWalletSettings(newExpandState);
|
setShowCryptoWalletSettings(newExpandState);
|
||||||
setShowBlockedAddressesSetting(newExpandState);
|
setShowBlockedAddressesSetting(newExpandState);
|
||||||
@@ -61,6 +64,13 @@ const SettingsModal = () => {
|
|||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{showAccountSettings && <AccountSettings />}
|
{showAccountSettings && <AccountSettings />}
|
||||||
|
<div className={`${styles.setting} ${styles.category}`}>
|
||||||
|
<label onClick={() => setShowAvatarSettings(!showAvatarSettings)}>
|
||||||
|
<span className={showAvatarSettings ? styles.hideButton : styles.showButton} />
|
||||||
|
{t('avatar')}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{showAvatarSettings && <AvatarSettings />}
|
||||||
<div className={`${styles.setting} ${styles.category}`}>
|
<div className={`${styles.setting} ${styles.category}`}>
|
||||||
<label onClick={() => setShowCryptoAddressSetting(!showCryptoAddressSetting)}>
|
<label onClick={() => setShowCryptoAddressSetting(!showCryptoAddressSetting)}>
|
||||||
<span className={showCryptoAddressSetting ? styles.hideButton : styles.showButton} />
|
<span className={showCryptoAddressSetting ? styles.hideButton : styles.showButton} />
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ const SubplebbitDescription = ({ avatarUrl, createdAt, description, shortAddress
|
|||||||
author: { displayName: `## ${t('board_mods')}` },
|
author: { displayName: `## ${t('board_mods')}` },
|
||||||
content: isInAllView ? multisubMetadata?.description : description,
|
content: isInAllView ? multisubMetadata?.description : description,
|
||||||
link: avatarUrl,
|
link: avatarUrl,
|
||||||
|
replyCount: 0,
|
||||||
title: t('welcome_to_board', {
|
title: t('welcome_to_board', {
|
||||||
board: isInAllView ? multisubMetadata?.title : title || `p/${shortAddress}`,
|
board: isInAllView ? multisubMetadata?.title : title || `p/${shortAddress}`,
|
||||||
interpolation: { escapeValue: false },
|
interpolation: { escapeValue: false },
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const SubplebbitRules = ({ subplebbitAddress, createdAt, rules }: RulesPostProps
|
|||||||
timestamp: createdAt,
|
timestamp: createdAt,
|
||||||
author: { displayName: `## ${t('board_mods')}` },
|
author: { displayName: `## ${t('board_mods')}` },
|
||||||
content,
|
content,
|
||||||
|
replyCount: 0,
|
||||||
title: _.capitalize(t('rules')),
|
title: _.capitalize(t('rules')),
|
||||||
pinned: true,
|
pinned: true,
|
||||||
locked: true,
|
locked: true,
|
||||||
|
|||||||
@@ -15,16 +15,16 @@ const SubplebbitStats = () => {
|
|||||||
const subplebbit = useSubplebbit({ subplebbitAddress });
|
const subplebbit = useSubplebbit({ subplebbitAddress });
|
||||||
const { address, createdAt } = subplebbit || {};
|
const { address, createdAt } = subplebbit || {};
|
||||||
|
|
||||||
|
const stats = useSubplebbitStats({ subplebbitAddress: address });
|
||||||
|
const [showStats, setShowStats] = useState(true);
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isInDescriptionView = isDescriptionView(location.pathname, params);
|
const isInDescriptionView = isDescriptionView(location.pathname, params);
|
||||||
const isInRulesView = isRulesView(location.pathname, params);
|
const isInRulesView = isRulesView(location.pathname, params);
|
||||||
|
|
||||||
const comment = useComment({ commentCid: params?.commentCid });
|
const comment = useComment({ commentCid: params?.commentCid });
|
||||||
const { deleted, locked, removed } = comment || {};
|
const { deleted, locked, removed } = comment || {};
|
||||||
const hideStats = deleted || locked || removed || isInDescriptionView || isInRulesView;
|
const hideStats = !stats.allPostCount || deleted || locked || removed || isInDescriptionView || isInRulesView;
|
||||||
|
|
||||||
const stats = useSubplebbitStats({ subplebbitAddress: address });
|
|
||||||
const [showStats, setShowStats] = useState(true);
|
|
||||||
|
|
||||||
const unixToMMDDYYYY = (timestamp: number) => {
|
const unixToMMDDYYYY = (timestamp: number) => {
|
||||||
const date = new Date(timestamp * 1000);
|
const date = new Date(timestamp * 1000);
|
||||||
|
|||||||
@@ -63,46 +63,49 @@ const TopBarDesktop = () => {
|
|||||||
|
|
||||||
const { plebbitSubs, interestsSubs, randomSubs, internationalSubs, projectsSubs } = categorizeSubplebbits(subplebbits);
|
const { plebbitSubs, interestsSubs, randomSubs, internationalSubs, projectsSubs } = categorizeSubplebbits(subplebbits);
|
||||||
|
|
||||||
const renderSubplebbits = (subplebbits: any) => {
|
const renderSubplebbits = (subplebbits: any) =>
|
||||||
return subplebbits.map((sub: any, index: any) => (
|
subplebbits.length > 0 && (
|
||||||
<span key={index}>
|
<>
|
||||||
{index === 0 ? null : ' '}
|
[
|
||||||
<Link to={`/p/${sub.address}${isInCatalogView ? '/catalog' : ''}`}>{sub.address.includes('.') ? sub.address : sub.address.slice(0, 10).concat('...')}</Link>
|
{subplebbits.map((sub: any, index: any) => (
|
||||||
{index !== subplebbits.length - 1 ? ' /' : null}
|
<span key={index}>
|
||||||
</span>
|
{index === 0 ? null : ' '}
|
||||||
));
|
<Link to={`/p/${sub.address}${isInCatalogView ? '/catalog' : ''}`}>{sub.address.includes('.') ? sub.address : sub.address.slice(0, 10).concat('...')}</Link>
|
||||||
};
|
{index !== subplebbits.length - 1 ? ' /' : null}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
]{' '}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.boardNavDesktop}>
|
<div className={styles.boardNavDesktop}>
|
||||||
<span className={styles.boardList}>
|
<span className={styles.boardList}>
|
||||||
[<Link to='/p/all'>all</Link> / <Link to='/p/subscriptions'>subscriptions</Link>]{' '}
|
[<Link to='/p/all'>all</Link> / <Link to='/p/subscriptions'>subscriptions</Link>] {renderSubplebbits(plebbitSubs)}
|
||||||
{subplebbits && (
|
{renderSubplebbits(projectsSubs)}
|
||||||
<>
|
{renderSubplebbits(interestsSubs)}
|
||||||
[{renderSubplebbits(plebbitSubs)}] [{renderSubplebbits(projectsSubs)}] [{renderSubplebbits(interestsSubs)}] [{renderSubplebbits(randomSubs)}] [
|
{renderSubplebbits(randomSubs)}
|
||||||
{renderSubplebbits(internationalSubs)}] [
|
{renderSubplebbits(internationalSubs)}[
|
||||||
<Link
|
<Link
|
||||||
to='boards/create'
|
to='boards/create'
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
alert('work in progress');
|
alert('work in progress');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Create
|
Create
|
||||||
</Link>
|
</Link>
|
||||||
] [
|
] [
|
||||||
<Link
|
<Link
|
||||||
to='boards'
|
to='boards'
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
alert('work in progress');
|
alert('work in progress');
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Vote
|
Vote
|
||||||
</Link>
|
</Link>
|
||||||
]
|
]
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</span>
|
</span>
|
||||||
<span className={styles.navTopRight}>
|
<span className={styles.navTopRight}>
|
||||||
[<Link to={!location.pathname.endsWith('settings') ? location.pathname.replace(/\/$/, '') + '/settings' : location.pathname}>{t('settings')}</Link>] [
|
[<Link to={!location.pathname.endsWith('settings') ? location.pathname.replace(/\/$/, '') + '/settings' : location.pathname}>{t('settings')}</Link>] [
|
||||||
|
|||||||
@@ -1,21 +1,25 @@
|
|||||||
import { useLocation, useParams } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import useThemeStore from '../stores/use-theme-store';
|
import useThemeStore from '../stores/use-theme-store';
|
||||||
import useDefaultSubplebbits from './use-default-subplebbits';
|
import useDefaultSubplebbits from './use-default-subplebbits';
|
||||||
import { isAllView, isHomeView, isNotFoundView, isSubscriptionsView } from '../lib/utils/view-utils';
|
import { isAllView, isHomeView, isNotFoundView, isPendingPostView, isSubscriptionsView } from '../lib/utils/view-utils';
|
||||||
import { nsfwTags } from '../views/home/home';
|
import { nsfwTags } from '../views/home/home';
|
||||||
|
|
||||||
const useInitialTheme = () => {
|
const useInitialTheme = () => {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
|
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
|
||||||
const getTheme = useThemeStore((state) => state.getTheme);
|
const getTheme = useThemeStore((state) => state.getTheme);
|
||||||
|
const currentTheme = useThemeStore((state) => state.currentTheme);
|
||||||
const subplebbits = useDefaultSubplebbits();
|
const subplebbits = useDefaultSubplebbits();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const isInHomeView = isHomeView(location.pathname);
|
const isInHomeView = isHomeView(location.pathname);
|
||||||
const isInNotFoundView = isNotFoundView(location.pathname, params);
|
const isInNotFoundView = isNotFoundView(location.pathname, params);
|
||||||
const isInAllView = isAllView(location.pathname, params);
|
const isInAllView = isAllView(location.pathname, params);
|
||||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
|
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
|
||||||
|
const isInPendingPostView = isPendingPostView(location.pathname, params);
|
||||||
|
|
||||||
if (isInAllView) {
|
if (isInPendingPostView) {
|
||||||
|
return currentTheme || 'yotsuba';
|
||||||
|
} else if (isInAllView) {
|
||||||
return getTheme('all') || 'yotsuba-b';
|
return getTheme('all') || 'yotsuba-b';
|
||||||
} else if (isInSubscriptionsView) {
|
} else if (isInSubscriptionsView) {
|
||||||
return getTheme('subscriptions') || 'yotsuba-b';
|
return getTheme('subscriptions') || 'yotsuba-b';
|
||||||
|
|||||||
@@ -1,19 +1,45 @@
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useEffect } from 'react';
|
||||||
import { Subplebbit } from '@plebbit/plebbit-react-hooks';
|
import { Subplebbit } from '@plebbit/plebbit-react-hooks';
|
||||||
import { getFormattedTimeAgo } from '../lib/utils/time-utils';
|
import { getFormattedTimeAgo } from '../lib/utils/time-utils';
|
||||||
|
import useSubplebbitOfflineStore from '../stores/use-subplebbit-offline-store';
|
||||||
|
|
||||||
const useIsSubplebbitOffline = (subplebbit: Subplebbit) => {
|
const useIsSubplebbitOffline = (subplebbit: Subplebbit) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { updatedAt, updatingState } = subplebbit || {};
|
const { address, state, updatedAt, updatingState } = subplebbit || {};
|
||||||
const isProbablyOffline = !updatedAt || (updatedAt && updatedAt < Date.now() / 1000 - 60 * 60);
|
const { subplebbitOfflineState, setSubplebbitOfflineState, initializesubplebbitOfflineState } = useSubplebbitOfflineStore();
|
||||||
const isDefinitelyOffline = updatingState === 'failed';
|
|
||||||
const isOffline = isProbablyOffline || isDefinitelyOffline;
|
|
||||||
|
|
||||||
const offlineTitle = updatedAt
|
useEffect(() => {
|
||||||
|
if (address && !subplebbitOfflineState[address]) {
|
||||||
|
initializesubplebbitOfflineState(address);
|
||||||
|
}
|
||||||
|
}, [address, subplebbitOfflineState, initializesubplebbitOfflineState]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (address) {
|
||||||
|
setSubplebbitOfflineState(address, { state, updatedAt, updatingState });
|
||||||
|
}
|
||||||
|
}, [address, state, updatedAt, updatingState, setSubplebbitOfflineState]);
|
||||||
|
|
||||||
|
const subplebbitOfflineStore = subplebbitOfflineState[address] || { initialLoad: true };
|
||||||
|
|
||||||
|
const isLoading =
|
||||||
|
updatingState === 'resolving-address' ||
|
||||||
|
(updatingState === 'stopped' && state === 'stopped' && !updatedAt) ||
|
||||||
|
(subplebbitOfflineStore.initialLoad && updatingState === 'fetching-ipns' && !updatedAt);
|
||||||
|
|
||||||
|
const isOffline =
|
||||||
|
!isLoading && ((updatedAt && updatedAt < Date.now() / 1000 - 60 * 60) || updatingState === 'failed' || (updatingState === 'fetching-ipns' && !updatedAt));
|
||||||
|
|
||||||
|
const offlineIconClass = isLoading ? 'yellowOfflineIcon' : isOffline ? 'redOfflineIcon' : '';
|
||||||
|
|
||||||
|
const offlineTitle = isLoading
|
||||||
|
? t('loading')
|
||||||
|
: updatedAt
|
||||||
? isOffline && t('posts_last_synced_info', { time: getFormattedTimeAgo(updatedAt), interpolation: { escapeValue: false } })
|
? isOffline && t('posts_last_synced_info', { time: getFormattedTimeAgo(updatedAt), interpolation: { escapeValue: false } })
|
||||||
: t('subplebbit_offline_info');
|
: t('subplebbit_offline_info');
|
||||||
|
|
||||||
return { isOffline, isDefinitelyOffline, offlineTitle };
|
return { isOffline, isOnlineStatusLoading: isLoading, offlineIconClass, offlineTitle };
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useIsSubplebbitOffline;
|
export default useIsSubplebbitOffline;
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ const useReplies = (comment: Comment) => {
|
|||||||
// filter against the original comment's CID and all CIDs in flattened replies
|
// filter against the original comment's CID and all CIDs in flattened replies
|
||||||
const filter = useCallback(
|
const filter = useCallback(
|
||||||
(accountComment: Comment) => {
|
(accountComment: Comment) => {
|
||||||
return accountComment.parentCid === comment?.cid || replyCids.has(accountComment.parentCid);
|
const parentCid = accountComment.parentCid || 'n/a';
|
||||||
|
const isCidInDocument = !!document.querySelector(`[data-cid="${parentCid}"]`);
|
||||||
|
return parentCid === (comment?.cid || 'n/a') || replyCids.has(parentCid) || isCidInDocument;
|
||||||
},
|
},
|
||||||
[comment?.cid, replyCids],
|
[comment?.cid, replyCids],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -38,6 +38,14 @@ hr {
|
|||||||
color-scheme: dark;
|
color-scheme: dark;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.yellowOfflineIcon {
|
||||||
|
background-image: url('/public/assets/icons/offline-yellow.png');
|
||||||
|
}
|
||||||
|
|
||||||
|
.redOfflineIcon {
|
||||||
|
background-image: url('/public/assets/icons/offline-red.png');
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.button {
|
.button {
|
||||||
font-size: var(--button-font-size-mobile);
|
font-size: var(--button-font-size-mobile);
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ i18next
|
|||||||
'eu',
|
'eu',
|
||||||
'fa',
|
'fa',
|
||||||
'fi',
|
'fi',
|
||||||
|
'fil',
|
||||||
'fr',
|
'fr',
|
||||||
'fy',
|
'fy',
|
||||||
'ga',
|
'ga',
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
interface SubplebbitOfflineState {
|
||||||
|
state?: string;
|
||||||
|
updatedAt?: number;
|
||||||
|
updatingState?: string;
|
||||||
|
initialLoad: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SubplebbitOfflineStore {
|
||||||
|
subplebbitOfflineState: Record<string, SubplebbitOfflineState>;
|
||||||
|
setSubplebbitOfflineState: (address: string, state: Partial<SubplebbitOfflineState>) => void;
|
||||||
|
initializesubplebbitOfflineState: (address: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useSubplebbitOfflineStore = create<SubplebbitOfflineStore>((set) => ({
|
||||||
|
subplebbitOfflineState: {},
|
||||||
|
setSubplebbitOfflineState: (address, newState) =>
|
||||||
|
set((state) => ({
|
||||||
|
subplebbitOfflineState: {
|
||||||
|
...state.subplebbitOfflineState,
|
||||||
|
[address]: {
|
||||||
|
...state.subplebbitOfflineState[address],
|
||||||
|
...newState,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
initializesubplebbitOfflineState: (address) => {
|
||||||
|
set((state) => ({
|
||||||
|
subplebbitOfflineState: {
|
||||||
|
...state.subplebbitOfflineState,
|
||||||
|
[address]: {
|
||||||
|
initialLoad: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
setTimeout(() => {
|
||||||
|
set((state) => ({
|
||||||
|
subplebbitOfflineState: {
|
||||||
|
...state.subplebbitOfflineState,
|
||||||
|
[address]: {
|
||||||
|
...state.subplebbitOfflineState[address],
|
||||||
|
initialLoad: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}, 7000);
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
export default useSubplebbitOfflineStore;
|
||||||
@@ -8,6 +8,7 @@ interface ThemeState {
|
|||||||
all: string;
|
all: string;
|
||||||
subscriptions: string;
|
subscriptions: string;
|
||||||
};
|
};
|
||||||
|
currentTheme: string | null;
|
||||||
setTheme: (category: keyof ThemeState['themes'], theme: string) => void;
|
setTheme: (category: keyof ThemeState['themes'], theme: string) => void;
|
||||||
getTheme: (category: keyof ThemeState['themes']) => string | null;
|
getTheme: (category: keyof ThemeState['themes']) => string | null;
|
||||||
loadThemes: () => Promise<void>;
|
loadThemes: () => Promise<void>;
|
||||||
@@ -25,15 +26,18 @@ const useThemeStore = create<ThemeState>((set: StoreApi<ThemeState>['setState'],
|
|||||||
all: 'yotsuba-b',
|
all: 'yotsuba-b',
|
||||||
subscriptions: 'yotsuba-b',
|
subscriptions: 'yotsuba-b',
|
||||||
},
|
},
|
||||||
|
currentTheme: null,
|
||||||
setTheme: async (category, theme) => {
|
setTheme: async (category, theme) => {
|
||||||
const currentThemes = get().themes;
|
const currentThemes = get().themes;
|
||||||
const updatedThemes = { ...currentThemes, [category]: theme };
|
const updatedThemes = { ...currentThemes, [category]: theme };
|
||||||
await themeStore.setItem(category, theme);
|
await themeStore.setItem(category, theme);
|
||||||
set({ themes: updatedThemes });
|
set({ themes: updatedThemes, currentTheme: theme });
|
||||||
},
|
},
|
||||||
getTheme: (category) => {
|
getTheme: (category) => {
|
||||||
const currentThemes = get().themes;
|
const currentThemes = get().themes;
|
||||||
return currentThemes[category] || null;
|
const theme = currentThemes[category] || null;
|
||||||
|
set({ currentTheme: theme });
|
||||||
|
return theme;
|
||||||
},
|
},
|
||||||
loadThemes: async () => {
|
loadThemes: async () => {
|
||||||
const entries: [keyof ThemeState['themes'], string][] = await themeStore.entries();
|
const entries: [keyof ThemeState['themes'], string][] = await themeStore.entries();
|
||||||
@@ -46,7 +50,7 @@ const useThemeStore = create<ThemeState>((set: StoreApi<ThemeState>['setState'],
|
|||||||
entries.forEach(([key, value]) => {
|
entries.forEach(([key, value]) => {
|
||||||
themes[key] = value;
|
themes[key] = value;
|
||||||
});
|
});
|
||||||
set({ themes });
|
set({ themes, currentTheme: null });
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
:root .yotsuba {
|
:root .yotsuba {
|
||||||
--color-scheme: light;
|
--color-scheme: light;
|
||||||
|
|
||||||
|
/* avatar */
|
||||||
|
--avatar-text-color: black;
|
||||||
|
|
||||||
/* backlink */
|
/* backlink */
|
||||||
--post-backlink-text-color: navy;
|
--post-backlink-text-color: navy;
|
||||||
@@ -415,6 +418,9 @@
|
|||||||
:root .futaba {
|
:root .futaba {
|
||||||
--color-scheme: light;
|
--color-scheme: light;
|
||||||
|
|
||||||
|
/* avatar */
|
||||||
|
--avatar-text-color: black;
|
||||||
|
|
||||||
/* backlink */
|
/* backlink */
|
||||||
--post-backlink-text-color: navy;
|
--post-backlink-text-color: navy;
|
||||||
--post-backlink-text-decoration: underline;
|
--post-backlink-text-decoration: underline;
|
||||||
@@ -772,6 +778,9 @@
|
|||||||
|
|
||||||
:root .tomorrow {
|
:root .tomorrow {
|
||||||
--color-scheme: dark;
|
--color-scheme: dark;
|
||||||
|
|
||||||
|
/* avatar */
|
||||||
|
--avatar-background-color: black;
|
||||||
|
|
||||||
/* backlink */
|
/* backlink */
|
||||||
--post-backlink-text-color: #5f89ac;
|
--post-backlink-text-color: #5f89ac;
|
||||||
|
|||||||
@@ -18,12 +18,11 @@ const Board = ({ subplebbit, subplebbits }: { subplebbit: Subplebbit; subplebbit
|
|||||||
const boardLink = useCatalog ? `/p/${address}/catalog` : `/p/${address}`;
|
const boardLink = useCatalog ? `/p/${address}/catalog` : `/p/${address}`;
|
||||||
|
|
||||||
const subplebbitData = subplebbits && subplebbits.find((sub: Subplebbit) => sub?.address === address);
|
const subplebbitData = subplebbits && subplebbits.find((sub: Subplebbit) => sub?.address === address);
|
||||||
const { isDefinitelyOffline, isOffline, offlineTitle } = useIsSubplebbitOffline(subplebbitData);
|
const { isOffline, isOnlineStatusLoading, offlineIconClass, offlineTitle } = useIsSubplebbitOffline(subplebbitData);
|
||||||
const iconClass = `${styles.offlineIcon} ${isDefinitelyOffline ? styles.redOfflineIcon : ''}`;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.subplebbit} key={address}>
|
<div className={styles.subplebbit} key={address}>
|
||||||
{isOffline && <span className={`${styles.offlineIcon} ${iconClass}`} title={offlineTitle} />}
|
{(isOffline || isOnlineStatusLoading) && <span className={`${styles.offlineIcon} ${offlineIconClass}`} title={offlineTitle} />}
|
||||||
<Link to={boardLink}>{title || address}</Link>
|
<Link to={boardLink}>{title || address}</Link>
|
||||||
{nsfwTag && <span className={styles.nsfw}> ({t(nsfwTag)})</span>}
|
{nsfwTag && <span className={styles.nsfw}> ({t(nsfwTag)})</span>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -176,7 +176,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.offlineIcon {
|
.offlineIcon {
|
||||||
background-image: url('/public/assets/icons/offline.png');
|
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
vertical-align: text-top;
|
vertical-align: text-top;
|
||||||
margin-right: 2px;
|
margin-right: 2px;
|
||||||
@@ -187,10 +186,6 @@
|
|||||||
image-rendering: pixelated;
|
image-rendering: pixelated;
|
||||||
}
|
}
|
||||||
|
|
||||||
.redOfflineIcon {
|
|
||||||
background-image: url('/public/assets/icons/offline-red.png') !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.boardsBoxContent {
|
.boardsBoxContent {
|
||||||
font-size: 93%;
|
font-size: 93%;
|
||||||
padding: 0.5em;
|
padding: 0.5em;
|
||||||
|
|||||||
@@ -185,6 +185,11 @@
|
|||||||
color: var(--post-mobile-abbr-text-color);
|
color: var(--post-mobile-abbr-text-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loadingString {
|
||||||
|
display: block;
|
||||||
|
color: var(--post-mobile-abbr-text-color);
|
||||||
|
}
|
||||||
|
|
||||||
.postDesktop .summary .omittedRepliesButtonWrapper {
|
.postDesktop .summary .omittedRepliesButtonWrapper {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 8px;
|
top: 8px;
|
||||||
@@ -499,6 +504,17 @@
|
|||||||
text-decoration: var(--post-content-link-text-decoration-hover);
|
text-decoration: var(--post-content-link-text-decoration-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.authorAvatar {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
background-size: 100%;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.replyQuotePreview {
|
.replyQuotePreview {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
@@ -511,6 +527,11 @@
|
|||||||
.replyDesktop {
|
.replyDesktop {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loadingString {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
@media (min-width: 640px) {
|
||||||
|
|||||||
Reference in New Issue
Block a user