mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
+1
-4
@@ -28,7 +28,7 @@
|
||||
"prettier": "3.0.3",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"react-draggable": "^4.4.6",
|
||||
"react-draggable": "4.4.6",
|
||||
"react-i18next": "13.2.2",
|
||||
"react-markdown": "8.0.6",
|
||||
"react-router-dom": "6.16.0",
|
||||
@@ -136,9 +136,6 @@
|
||||
"category": "Network"
|
||||
}
|
||||
},
|
||||
"resolutions": {
|
||||
"styled-components": "^5"
|
||||
},
|
||||
"lint-staged": {
|
||||
"{src,electron}/**/*.{js,ts,tsx}": [
|
||||
"prettier --write"
|
||||
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "الاسم",
|
||||
"comment": "تعليق",
|
||||
"subject": "الموضوع",
|
||||
"anonymous": "مجهول"
|
||||
"anonymous": "مجهول",
|
||||
"image": "صورة",
|
||||
"video": "فيديو",
|
||||
"audio": "صوت",
|
||||
"webpage": "صفحة ويب",
|
||||
"iframe": "إطار",
|
||||
"rules": "القواعد",
|
||||
"welcome_to_board": "مرحبًا بك في {{board}}",
|
||||
"comment_too_long": "التعليق طويل جدًا. <1>اضغط هنا</1> لعرض النص الكامل.",
|
||||
"replies_omitted": "{{repliesCount}} ردود محذوفة. <1>اضغط هنا</1> لعرضها.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} ردود و{{linksCount}} روابط محذوفة. <1>اضغط هنا</1> لعرضها.",
|
||||
"thread_closed_alert": "الموضوع مغلق"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "নাম",
|
||||
"comment": "মন্তব্য",
|
||||
"subject": "বিষয়",
|
||||
"anonymous": "অজানা"
|
||||
"anonymous": "অজানা",
|
||||
"image": "ছবি",
|
||||
"video": "ভিডিও",
|
||||
"audio": "অডিও",
|
||||
"webpage": "ওয়েবপেজ",
|
||||
"iframe": "আইফ্রেম",
|
||||
"rules": "নিয়ম",
|
||||
"welcome_to_board": "স্বাগতম {{board}}",
|
||||
"comment_too_long": "মন্তব্য খুব দীর্ঘ। <1>পূর্ণ পাঠ্য দেখতে এখানে ক্লিক করুন</1>।",
|
||||
"replies_omitted": "{{repliesCount}}টি উত্তর বাদ দেওয়া হয়েছে। <1>দেখতে এখানে ক্লিক করুন</1>।",
|
||||
"replies_and_links_omitted": "{{repliesCount}}টি উত্তর এবং {{linksCount}}টি লিঙ্ক বাদ দেওয়া হয়েছে। <1>দেখতে এখানে ক্লিক করুন</1>।",
|
||||
"thread_closed_alert": "থ্রেডটি বন্ধ করা হয়েছে"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "název",
|
||||
"comment": "komentář",
|
||||
"subject": "předmět",
|
||||
"anonymous": "anonymní"
|
||||
"anonymous": "anonymní",
|
||||
"image": "obrázek",
|
||||
"video": "video",
|
||||
"audio": "audio",
|
||||
"webpage": "webová stránka",
|
||||
"iframe": "iframe",
|
||||
"rules": "pravidla",
|
||||
"welcome_to_board": "Vítejte na {{board}}",
|
||||
"comment_too_long": "Komentář je příliš dlouhý. <1>Klikněte zde</1> pro zobrazení celého textu.",
|
||||
"replies_omitted": "{{repliesCount}} odpovědí vynecháno. <1>Klikněte zde</1> pro zobrazení.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} odpovědí a {{linksCount}} odkazů vynecháno. <1>Klikněte zde</1> pro zobrazení.",
|
||||
"thread_closed_alert": "Vlákno je uzavřeno"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "navn",
|
||||
"comment": "kommentar",
|
||||
"subject": "emne",
|
||||
"anonymous": "anonym"
|
||||
"anonymous": "anonym",
|
||||
"image": "billede",
|
||||
"video": "video",
|
||||
"audio": "lyd",
|
||||
"webpage": "websted",
|
||||
"iframe": "iframe",
|
||||
"rules": "regler",
|
||||
"welcome_to_board": "Velkommen til {{board}}",
|
||||
"comment_too_long": "Kommentaren er for lang. <1>Klik her</1> for at se hele teksten.",
|
||||
"replies_omitted": "{{repliesCount}} svar udeladt. <1>Klik her</1> for at se dem.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} svar og {{linksCount}} links udeladt. <1>Klik her</1> for at se dem.",
|
||||
"thread_closed_alert": "Tråden er lukket"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "Name",
|
||||
"comment": "Kommentar",
|
||||
"subject": "Betreff",
|
||||
"anonymous": "anonym"
|
||||
"anonymous": "anonym",
|
||||
"image": "Bild",
|
||||
"video": "Video",
|
||||
"audio": "Audio",
|
||||
"webpage": "Webseite",
|
||||
"iframe": "iframe",
|
||||
"rules": "Regeln",
|
||||
"welcome_to_board": "Willkommen bei {{board}}",
|
||||
"comment_too_long": "Kommentar zu lang. <1>Klicken Sie hier</1>, um den vollständigen Text anzuzeigen.",
|
||||
"replies_omitted": "{{repliesCount}} Antworten ausgelassen. <1>Klicken Sie hier</1>, um sie anzuzeigen.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} Antworten und {{linksCount}} Links ausgelassen. <1>Klicken Sie hier</1>, um sie anzuzeigen.",
|
||||
"thread_closed_alert": "Der Thread ist geschlossen"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "όνομα",
|
||||
"comment": "σχόλιο",
|
||||
"subject": "θέμα",
|
||||
"anonymous": "ανώνυμος"
|
||||
"anonymous": "ανώνυμος",
|
||||
"image": "εικόνα",
|
||||
"video": "βίντεο",
|
||||
"audio": "ήχος",
|
||||
"webpage": "ιστοσελίδα",
|
||||
"iframe": "πλαίσιο",
|
||||
"rules": "κανόνες",
|
||||
"welcome_to_board": "Καλώς ήρθατε στο {{board}}",
|
||||
"comment_too_long": "Το σχόλιο είναι πολύ μακροσκελές. <1>Κάντε κλικ εδώ</1> για να δείτε το πλήρες κείμενο.",
|
||||
"replies_omitted": "{{repliesCount}} απαντήσεις παραλείφθηκαν. <1>Κάντε κλικ εδώ</1> για να τις δείτε.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} απαντήσεις και {{linksCount}} σύνδεσμοι παραλείφθηκαν. <1>Κάντε κλικ εδώ</1> για να τις δείτε.",
|
||||
"thread_closed_alert": "Το νήμα έχει κλείσει"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "name",
|
||||
"comment": "comment",
|
||||
"subject": "subject",
|
||||
"anonymous": "anonymous"
|
||||
"anonymous": "anonymous",
|
||||
"image": "image",
|
||||
"video": "video",
|
||||
"audio": "audio",
|
||||
"webpage": "webpage",
|
||||
"iframe": "iframe",
|
||||
"rules": "rules",
|
||||
"welcome_to_board": "Welcome to {{board}}",
|
||||
"comment_too_long": "Comment too long. <1>Click here</1> to view the full text.",
|
||||
"replies_omitted": "{{repliesCount}} replies omitted. <1>Click here</1> to view.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} replies and {{linksCount}} links omitted. <1>Click here</1> to view.",
|
||||
"thread_closed_alert": "This thread is closed"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "nombre",
|
||||
"comment": "comentario",
|
||||
"subject": "asunto",
|
||||
"anonymous": "anónimo"
|
||||
"anonymous": "anónimo",
|
||||
"image": "imagen",
|
||||
"video": "video",
|
||||
"audio": "audio",
|
||||
"webpage": "página web",
|
||||
"iframe": "iframe",
|
||||
"rules": "reglas",
|
||||
"welcome_to_board": "Bienvenido a {{board}}",
|
||||
"comment_too_long": "Comentario demasiado largo. <1>Haz clic aquí</1> para ver el texto completo.",
|
||||
"replies_omitted": "{{repliesCount}} respuestas omitidas. <1>Haz clic aquí</1> para verlas.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} respuestas y {{linksCount}} enlaces omitidos. <1>Haz clic aquí</1> para verlos.",
|
||||
"thread_closed_alert": "El hilo está cerrado"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "نام",
|
||||
"comment": "نظر",
|
||||
"subject": "موضوع",
|
||||
"anonymous": "ناشناس"
|
||||
"anonymous": "ناشناس",
|
||||
"image": "تصویر",
|
||||
"video": "ویدیو",
|
||||
"audio": "صدا",
|
||||
"webpage": "صفحه وب",
|
||||
"iframe": "فریم",
|
||||
"rules": "قوانین",
|
||||
"welcome_to_board": "خوش آمدید به {{board}}",
|
||||
"comment_too_long": "نظر بسیار طولانی است. <1>اینجا کلیک کنید</1> تا متن کامل را ببینید.",
|
||||
"replies_omitted": "{{repliesCount}} پاسخ حذف شده است. <1>اینجا کلیک کنید</1> تا آنها را مشاهده کنید.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} پاسخ و {{linksCount}} لینک حذف شده است. <1>اینجا کلیک کنید</1> تا آنها را مشاهده کنید.",
|
||||
"thread_closed_alert": "موضوع بسته شده است"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "nimi",
|
||||
"comment": "kommentti",
|
||||
"subject": "aihe",
|
||||
"anonymous": "anonyymi"
|
||||
"anonymous": "anonyymi",
|
||||
"image": "kuva",
|
||||
"video": "video",
|
||||
"audio": "audio",
|
||||
"webpage": "verkkosivu",
|
||||
"iframe": "iframe",
|
||||
"rules": "säännöt",
|
||||
"welcome_to_board": "Tervetuloa {{board}}",
|
||||
"comment_too_long": "Kommentti on liian pitkä. <1>Napsauta tästä</1> nähdäksesi koko tekstin.",
|
||||
"replies_omitted": "{{repliesCount}} vastausta jätetty pois. <1>Napsauta tästä</1> nähdäksesi ne.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} vastausta ja {{linksCount}} linkkiä jätetty pois. <1>Napsauta tästä</1> nähdäksesi ne.",
|
||||
"thread_closed_alert": "Ketju on suljettu"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "pangalan",
|
||||
"comment": "komento",
|
||||
"subject": "paksa",
|
||||
"anonymous": "anonimo"
|
||||
"anonymous": "anonimo",
|
||||
"image": "larawan",
|
||||
"video": "video",
|
||||
"audio": "audio",
|
||||
"webpage": "pahinahon",
|
||||
"iframe": "iframe",
|
||||
"rules": "mga patakaran",
|
||||
"welcome_to_board": "Maligayang pagdating sa {{board}}",
|
||||
"comment_too_long": "Masyadong mahaba ang komento. <1>I-click dito</1> upang makita ang buong teksto.",
|
||||
"replies_omitted": "{{repliesCount}} mga tugon ang hindi ipinakita. <1>I-click dito</1> upang makita.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} mga tugon at {{linksCount}} mga link ang hindi ipinakita. <1>I-click dito</1> upang makita.",
|
||||
"thread_closed_alert": "Sarado na ang thread"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "nom",
|
||||
"comment": "commentaire",
|
||||
"subject": "sujet",
|
||||
"anonymous": "anonyme"
|
||||
"anonymous": "anonyme",
|
||||
"image": "image",
|
||||
"video": "vidéo",
|
||||
"audio": "audio",
|
||||
"webpage": "page web",
|
||||
"iframe": "iframe",
|
||||
"rules": "règles",
|
||||
"welcome_to_board": "Bienvenue sur {{board}}",
|
||||
"comment_too_long": "Commentaire trop long. <1>Cliquez ici</1> pour voir le texte complet.",
|
||||
"replies_omitted": "{{repliesCount}} réponses omises. <1>Cliquez ici</1> pour les afficher.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} réponses et {{linksCount}} liens omis. <1>Cliquez ici</1> pour les afficher.",
|
||||
"thread_closed_alert": "Le fil est fermé"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "שם",
|
||||
"comment": "תגובה",
|
||||
"subject": "נושא",
|
||||
"anonymous": "אנונימי"
|
||||
"anonymous": "אנונימי",
|
||||
"image": "תמונה",
|
||||
"video": "וידאו",
|
||||
"audio": "שמע",
|
||||
"webpage": "דף אינטרנט",
|
||||
"iframe": "מסגרת",
|
||||
"rules": "חוקים",
|
||||
"welcome_to_board": "ברוך הבא ל{{board}}",
|
||||
"comment_too_long": "התגובה ארוכה מדי. <1>לחץ כאן</1> כדי לראות את הטקסט המלא.",
|
||||
"replies_omitted": "{{repliesCount}} תגובות נשמטו. <1>לחץ כאן</1> לצפייה.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} תגובות ו{{linksCount}} קישורים נשמטו. <1>לחץ כאן</1> לצפייה.",
|
||||
"thread_closed_alert": "האשכול סגור"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "नाम",
|
||||
"comment": "टिप्पणी",
|
||||
"subject": "विषय",
|
||||
"anonymous": "अनाम"
|
||||
"anonymous": "अनाम",
|
||||
"image": "चित्र",
|
||||
"video": "वीडियो",
|
||||
"audio": "ऑडियो",
|
||||
"webpage": "वेबपेज",
|
||||
"iframe": "आइफ्रेम",
|
||||
"rules": "नियम",
|
||||
"welcome_to_board": "{{board}} में आपका स्वागत है",
|
||||
"comment_too_long": "टिप्पणी बहुत लंबी है। <1>पूरा टेक्स्ट देखने के लिए यहां क्लिक करें</1>।",
|
||||
"replies_omitted": "{{repliesCount}} उत्तर छोड़े गए हैं। <1>देखने के लिए यहां क्लिक करें</1>।",
|
||||
"replies_and_links_omitted": "{{repliesCount}} उत्तर और {{linksCount}} लिंक छोड़े गए हैं। <1>देखने के लिए यहां क्लिक करें</1>।",
|
||||
"thread_closed_alert": "यह थ्रेड बंद है"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "név",
|
||||
"comment": "megjegyzés",
|
||||
"subject": "tárgy",
|
||||
"anonymous": "névtelen"
|
||||
"anonymous": "névtelen",
|
||||
"image": "kép",
|
||||
"video": "videó",
|
||||
"audio": "hang",
|
||||
"webpage": "weboldal",
|
||||
"iframe": "iframe",
|
||||
"rules": "szabályok",
|
||||
"welcome_to_board": "Üdvözöljük a(z) {{board}} oldalon",
|
||||
"comment_too_long": "A megjegyzés túl hosszú. <1>Kattintson ide</1> a teljes szöveg megtekintéséhez.",
|
||||
"replies_omitted": "{{repliesCount}} válasz kihagyva. <1>Kattintson ide</1> a megtekintéshez.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} válasz és {{linksCount}} link kihagyva. <1>Kattintson ide</1> a megtekintéshez.",
|
||||
"thread_closed_alert": "A téma lezárva"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "nama",
|
||||
"comment": "komentar",
|
||||
"subject": "subjek",
|
||||
"anonymous": "anonim"
|
||||
"anonymous": "anonim",
|
||||
"image": "gambar",
|
||||
"video": "video",
|
||||
"audio": "audio",
|
||||
"webpage": "halaman web",
|
||||
"iframe": "iframe",
|
||||
"rules": "aturan",
|
||||
"welcome_to_board": "Selamat datang di {{board}}",
|
||||
"comment_too_long": "Komentar terlalu panjang. <1>Klik di sini</1> untuk melihat teks lengkap.",
|
||||
"replies_omitted": "{{repliesCount}} balasan dihilangkan. <1>Klik di sini</1> untuk melihat.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} balasan dan {{linksCount}} tautan dihilangkan. <1>Klik di sini</1> untuk melihat.",
|
||||
"thread_closed_alert": "Thread ini ditutup"
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
"whitepaper": "Libro bianco",
|
||||
"token": "Token",
|
||||
"download_app": "Scarica app",
|
||||
"start_new_thread": "Inizia un Nuovo Thread",
|
||||
"start_new_thread": "Invia un Nuovo Thread",
|
||||
"show_stats": "Mostra Statistiche",
|
||||
"hide": "Nascondi",
|
||||
"board_stats_hour": "Nell'ultima ora, <1>{{userCount}}</1> utenti hanno inviato <1>{{postCount}}</1> post",
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "nome",
|
||||
"comment": "commento",
|
||||
"subject": "oggetto",
|
||||
"anonymous": "anonimo"
|
||||
"anonymous": "anonimo",
|
||||
"image": "immagine",
|
||||
"video": "video",
|
||||
"audio": "audio",
|
||||
"webpage": "pagina web",
|
||||
"iframe": "iframe",
|
||||
"rules": "regole",
|
||||
"welcome_to_board": "Benvenuti su {{board}}",
|
||||
"comment_too_long": "Commento troppo lungo. <1>Clicca qui</1> per vedere il testo completo.",
|
||||
"replies_omitted": "{{repliesCount}} risposte omesse. <1>Clicca qui</1> per visualizzare.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} risposte e {{linksCount}} link omessi. <1>Clicca qui</1> per visualizzare.",
|
||||
"thread_closed_alert": "Questo thread è chiuso"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "名前",
|
||||
"comment": "コメント",
|
||||
"subject": "件名",
|
||||
"anonymous": "匿名"
|
||||
"anonymous": "匿名",
|
||||
"image": "画像",
|
||||
"video": "ビデオ",
|
||||
"audio": "音声",
|
||||
"webpage": "ウェブページ",
|
||||
"iframe": "アイフレーム",
|
||||
"rules": "ルール",
|
||||
"welcome_to_board": "{{board}}へようこそ",
|
||||
"comment_too_long": "コメントが長すぎます。 <1>ここをクリック</1>して全文を表示。",
|
||||
"replies_omitted": "{{repliesCount}}件の返信が省略されました。 <1>ここをクリック</1>して表示。",
|
||||
"replies_and_links_omitted": "{{repliesCount}}件の返信と{{linksCount}}件のリンクが省略されました。 <1>ここをクリック</1>して表示。",
|
||||
"thread_closed_alert": "このスレッドは閉じられています"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "이름",
|
||||
"comment": "댓글",
|
||||
"subject": "제목",
|
||||
"anonymous": "익명"
|
||||
"anonymous": "익명",
|
||||
"image": "이미지",
|
||||
"video": "비디오",
|
||||
"audio": "오디오",
|
||||
"webpage": "웹 페이지",
|
||||
"iframe": "iframe",
|
||||
"rules": "규칙",
|
||||
"welcome_to_board": "{{board}}에 오신 것을 환영합니다",
|
||||
"comment_too_long": "댓글이 너무 깁니다. <1>여기를 클릭하십시오</1> 전체 텍스트를 보려면.",
|
||||
"replies_omitted": "{{repliesCount}}개의 답글이 생략되었습니다. <1>여기를 클릭하십시오</1> 보려면.",
|
||||
"replies_and_links_omitted": "{{repliesCount}}개의 답글과 {{linksCount}}개의 링크가 생략되었습니다. <1>여기를 클릭하십시오</1> 보려면.",
|
||||
"thread_closed_alert": "이 스레드는 닫혔습니다"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "नाव",
|
||||
"comment": "टिप्पणी",
|
||||
"subject": "विषय",
|
||||
"anonymous": "अनाम"
|
||||
"anonymous": "अनाम",
|
||||
"image": "चित्र",
|
||||
"video": "व्हिडिओ",
|
||||
"audio": "ऑडिओ",
|
||||
"webpage": "वेबपेज",
|
||||
"iframe": "आयफ्रेम",
|
||||
"rules": "नियम",
|
||||
"welcome_to_board": "{{board}} वर आपले स्वागत आहे",
|
||||
"comment_too_long": "टिप्पणी खूप लांब आहे. <1>पूर्ण मजकूर पाहण्यासाठी येथे क्लिक करा</1>.",
|
||||
"replies_omitted": "{{repliesCount}} प्रतिसाद वगळले गेले आहेत. <1>पाहण्यासाठी येथे क्लिक करा</1>.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} प्रतिसाद आणि {{linksCount}} दुवे वगळले गेले आहेत. <1>पाहण्यासाठी येथे क्लिक करा</1>.",
|
||||
"thread_closed_alert": "हा थ्रेड बंद आहे"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "naam",
|
||||
"comment": "opmerking",
|
||||
"subject": "onderwerp",
|
||||
"anonymous": "anoniem"
|
||||
"anonymous": "anoniem",
|
||||
"image": "afbeelding",
|
||||
"video": "video",
|
||||
"audio": "audio",
|
||||
"webpage": "webpagina",
|
||||
"iframe": "iframe",
|
||||
"rules": "regels",
|
||||
"welcome_to_board": "Welkom bij {{board}}",
|
||||
"comment_too_long": "Reactie te lang. <1>Klik hier</1> om de volledige tekst te bekijken.",
|
||||
"replies_omitted": "{{repliesCount}} reacties weggelaten. <1>Klik hier</1> om ze te bekijken.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} reacties en {{linksCount}} links weggelaten. <1>Klik hier</1> om ze te bekijken.",
|
||||
"thread_closed_alert": "De thread is gesloten"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "navn",
|
||||
"comment": "kommentar",
|
||||
"subject": "emne",
|
||||
"anonymous": "anonym"
|
||||
"anonymous": "anonym",
|
||||
"image": "bilde",
|
||||
"video": "video",
|
||||
"audio": "lyd",
|
||||
"webpage": "nettside",
|
||||
"iframe": "iframe",
|
||||
"rules": "regler",
|
||||
"welcome_to_board": "Velkommen til {{board}}",
|
||||
"comment_too_long": "Kommentaren er for lang. <1>Klikk her</1> for å se hele teksten.",
|
||||
"replies_omitted": "{{repliesCount}} svar utelatt. <1>Klikk her</1> for å se dem.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} svar og {{linksCount}} lenker utelatt. <1>Klikk her</1> for å se dem.",
|
||||
"thread_closed_alert": "Tråden er stengt"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "nazwa",
|
||||
"comment": "komentarz",
|
||||
"subject": "temat",
|
||||
"anonymous": "anonimowy"
|
||||
"anonymous": "anonimowy",
|
||||
"image": "obraz",
|
||||
"video": "wideo",
|
||||
"audio": "audio",
|
||||
"webpage": "strona internetowa",
|
||||
"iframe": "iframe",
|
||||
"rules": "zasady",
|
||||
"welcome_to_board": "Witaj na {{board}}",
|
||||
"comment_too_long": "Komentarz jest za długi. <1>Kliknij tutaj</1>, aby zobaczyć pełny tekst.",
|
||||
"replies_omitted": "Pominięto {{repliesCount}} odpowiedzi. <1>Kliknij tutaj</1>, aby je zobaczyć.",
|
||||
"replies_and_links_omitted": "Pominięto {{repliesCount}} odpowiedzi i {{linksCount}} linków. <1>Kliknij tutaj</1>, aby je zobaczyć.",
|
||||
"thread_closed_alert": "Wątek jest zamknięty"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "nome",
|
||||
"comment": "comentário",
|
||||
"subject": "assunto",
|
||||
"anonymous": "anônimo"
|
||||
"anonymous": "anônimo",
|
||||
"image": "imagem",
|
||||
"video": "vídeo",
|
||||
"audio": "áudio",
|
||||
"webpage": "página da web",
|
||||
"iframe": "iframe",
|
||||
"rules": "regras",
|
||||
"welcome_to_board": "Bem-vindo ao {{board}}",
|
||||
"comment_too_long": "Comentário muito longo. <1>Clique aqui</1> para ver o texto completo.",
|
||||
"replies_omitted": "{{repliesCount}} respostas omitidas. <1>Clique aqui</1> para visualizar.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} respostas e {{linksCount}} links omitidos. <1>Clique aqui</1> para visualizar.",
|
||||
"thread_closed_alert": "O tópico está fechado"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "nume",
|
||||
"comment": "comentariu",
|
||||
"subject": "subiect",
|
||||
"anonymous": "anonim"
|
||||
"anonymous": "anonim",
|
||||
"image": "imagine",
|
||||
"video": "video",
|
||||
"audio": "audio",
|
||||
"webpage": "pagină web",
|
||||
"iframe": "iframe",
|
||||
"rules": "reguli",
|
||||
"welcome_to_board": "Bine ați venit la {{board}}",
|
||||
"comment_too_long": "Comentariul este prea lung. <1>Click aici</1> pentru a vedea textul complet.",
|
||||
"replies_omitted": "{{repliesCount}} răspunsuri omise. <1>Click aici</1> pentru a le vizualiza.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} răspunsuri și {{linksCount}} linkuri omise. <1>Click aici</1> pentru a le vizualiza.",
|
||||
"thread_closed_alert": "Acest fir de discuție este închis"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "имя",
|
||||
"comment": "комментарий",
|
||||
"subject": "тема",
|
||||
"anonymous": "анонимный"
|
||||
"anonymous": "анонимный",
|
||||
"image": "изображение",
|
||||
"video": "видео",
|
||||
"audio": "аудио",
|
||||
"webpage": "веб-страница",
|
||||
"iframe": "iframe",
|
||||
"rules": "правила",
|
||||
"welcome_to_board": "Добро пожаловать на {{board}}",
|
||||
"comment_too_long": "Комментарий слишком длинный. <1>Нажмите здесь</1>, чтобы прочитать полный текст.",
|
||||
"replies_omitted": "{{repliesCount}} ответов пропущено. <1>Нажмите здесь</1>, чтобы посмотреть.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} ответов и {{linksCount}} ссылок пропущено. <1>Нажмите здесь</1>, чтобы посмотреть.",
|
||||
"thread_closed_alert": "Тема закрыта"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "emri",
|
||||
"comment": "koment",
|
||||
"subject": "subjekt",
|
||||
"anonymous": "anonim"
|
||||
"anonymous": "anonim",
|
||||
"image": "imazh",
|
||||
"video": "video",
|
||||
"audio": "audio",
|
||||
"webpage": "faqja web",
|
||||
"iframe": "iframe",
|
||||
"rules": "rregullat",
|
||||
"welcome_to_board": "Mirë se vini te {{board}}",
|
||||
"comment_too_long": "Komenti është shumë i gjatë. <1>Kliko këtu</1> për të parë tekstin e plotë.",
|
||||
"replies_omitted": "{{repliesCount}} përgjigje janë lënë jashtë. <1>Kliko këtu</1> për t'i shikuar.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} përgjigje dhe {{linksCount}} lidhje janë lënë jashtë. <1>Kliko këtu</1> për t'i shikuar.",
|
||||
"thread_closed_alert": "Temë është mbyllur"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "namn",
|
||||
"comment": "kommentar",
|
||||
"subject": "ämne",
|
||||
"anonymous": "anonym"
|
||||
"anonymous": "anonym",
|
||||
"image": "bild",
|
||||
"video": "video",
|
||||
"audio": "ljud",
|
||||
"webpage": "webbsida",
|
||||
"iframe": "iframe",
|
||||
"rules": "regler",
|
||||
"welcome_to_board": "Välkommen till {{board}}",
|
||||
"comment_too_long": "Kommentaren är för lång. <1>Klicka här</1> för att se hela texten.",
|
||||
"replies_omitted": "{{repliesCount}} svar utelämnade. <1>Klicka här</1> för att visa dem.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} svar och {{linksCount}} länkar utelämnade. <1>Klicka här</1> för att visa dem.",
|
||||
"thread_closed_alert": "Tråden är stängd"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "పేరు",
|
||||
"comment": "వ్యాఖ్య",
|
||||
"subject": "విషయం",
|
||||
"anonymous": "అనామధుడు"
|
||||
"anonymous": "అనామధుడు",
|
||||
"image": "చిత్రం",
|
||||
"video": "వీడియో",
|
||||
"audio": "ఆడియో",
|
||||
"webpage": "వెబ్ పేజీ",
|
||||
"iframe": "iframe",
|
||||
"rules": "నియమాలు",
|
||||
"welcome_to_board": "{{board}} కు స్వాగతం",
|
||||
"comment_too_long": "వ్యాఖ్య చాలా పొడవుగా ఉంది. <1>పూర్తి టెక్స్ట్ చూడటానికి ఇక్కడ క్లిక్ చేయండి</1>.",
|
||||
"replies_omitted": "{{repliesCount}} స్పందనలు వదిలివేయబడ్డాయి. <1>చూడటానికి ఇక్కడ క్లిక్ చేయండి</1>.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} స్పందనలు మరియు {{linksCount}} లింకులు వదిలివేయబడ్డాయి. <1>చూడటానికి ఇక్కడ క్లిక్ చేయండి</1>.",
|
||||
"thread_closed_alert": "ఈ థ్రెడ్ మూసివేయబడింది"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "ชื่อ",
|
||||
"comment": "ความคิดเห็น",
|
||||
"subject": "หัวข้อ",
|
||||
"anonymous": "นิรนาม"
|
||||
"anonymous": "นิรนาม",
|
||||
"image": "ภาพ",
|
||||
"video": "วิดีโอ",
|
||||
"audio": "เสียง",
|
||||
"webpage": "หน้าเว็บ",
|
||||
"iframe": "iframe",
|
||||
"rules": "กฎ",
|
||||
"welcome_to_board": "ยินดีต้อนรับสู่ {{board}}",
|
||||
"comment_too_long": "ความคิดเห็นยาวเกินไป <1>คลิกที่นี่</1> เพื่อดูข้อความเต็ม.",
|
||||
"replies_omitted": "ข้าม {{repliesCount}} คำตอบ <1>คลิกที่นี่</1> เพื่อดู.",
|
||||
"replies_and_links_omitted": "ข้าม {{repliesCount}} คำตอบและ {{linksCount}} ลิงก์ <1>คลิกที่นี่</1> เพื่อดู.",
|
||||
"thread_closed_alert": "กระทู้นี้ถูกปิด"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "isim",
|
||||
"comment": "yorum",
|
||||
"subject": "konu",
|
||||
"anonymous": "anonim"
|
||||
"anonymous": "anonim",
|
||||
"image": "görüntü",
|
||||
"video": "video",
|
||||
"audio": "ses",
|
||||
"webpage": "web sayfası",
|
||||
"iframe": "iframe",
|
||||
"rules": "kurallar",
|
||||
"welcome_to_board": "{{board}}'a hoş geldiniz",
|
||||
"comment_too_long": "Yorum çok uzun. <1>Buraya tıklayın</1> tam metni görüntülemek için.",
|
||||
"replies_omitted": "{{repliesCount}} yanıt göz ardı edildi. <1>Buraya tıklayın</1> görüntülemek için.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} yanıt ve {{linksCount}} bağlantı göz ardı edildi. <1>Buraya tıklayın</1> görüntülemek için.",
|
||||
"thread_closed_alert": "Bu konu kapatılmıştır"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "ім'я",
|
||||
"comment": "коментар",
|
||||
"subject": "тема",
|
||||
"anonymous": "анонім"
|
||||
"anonymous": "анонім",
|
||||
"image": "зображення",
|
||||
"video": "відео",
|
||||
"audio": "аудіо",
|
||||
"webpage": "веб-сторінка",
|
||||
"iframe": "iframe",
|
||||
"rules": "правила",
|
||||
"welcome_to_board": "Ласкаво просимо до {{board}}",
|
||||
"comment_too_long": "Коментар занадто довгий. <1>Натисніть тут</1>, щоб переглянути повний текст.",
|
||||
"replies_omitted": "{{repliesCount}} відповідей пропущено. <1>Натисніть тут</1>, щоб переглянути.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} відповідей та {{linksCount}} посилань пропущено. <1>Натисніть тут</1>, щоб переглянути.",
|
||||
"thread_closed_alert": "Тему закрито"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "نام",
|
||||
"comment": "تبصرہ",
|
||||
"subject": "موضوع",
|
||||
"anonymous": "ناشناس"
|
||||
"anonymous": "ناشناس",
|
||||
"image": "تصویر",
|
||||
"video": "ویڈیو",
|
||||
"audio": "آڈیو",
|
||||
"webpage": "ویب پیج",
|
||||
"iframe": "آئی فریم",
|
||||
"rules": "اصول",
|
||||
"welcome_to_board": "{{board}} میں خوش آمدید",
|
||||
"comment_too_long": "تبصرہ بہت طویل ہے۔ <1>مکمل متن دیکھنے کے لیے یہاں کلک کریں</1>۔",
|
||||
"replies_omitted": "{{repliesCount}} جوابات چھوڑ دیے گئے ہیں۔ <1>مکمل دیکھنے کے لیے یہاں کلک کریں</1>۔",
|
||||
"replies_and_links_omitted": "{{repliesCount}} جوابات اور {{linksCount}} لنکس چھوڑ دیے گئے ہیں۔ <1>مکمل دیکھنے کے لیے یہاں کلک کریں</1>۔",
|
||||
"thread_closed_alert": "یہ دھاگہ بند ہے"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "tên",
|
||||
"comment": "bình luận",
|
||||
"subject": "chủ đề",
|
||||
"anonymous": "ẩn danh"
|
||||
"anonymous": "ẩn danh",
|
||||
"image": "hình ảnh",
|
||||
"video": "video",
|
||||
"audio": "âm thanh",
|
||||
"webpage": "trang web",
|
||||
"iframe": "iframe",
|
||||
"rules": "quy tắc",
|
||||
"welcome_to_board": "Chào mừng bạn đến với {{board}}",
|
||||
"comment_too_long": "Bình luận quá dài. <1>Nhấn vào đây</1> để xem toàn bộ văn bản.",
|
||||
"replies_omitted": "{{repliesCount}} câu trả lời bị bỏ qua. <1>Nhấn vào đây</1> để xem.",
|
||||
"replies_and_links_omitted": "{{repliesCount}} câu trả lời và {{linksCount}} liên kết bị bỏ qua. <1>Nhấn vào đây</1> để xem.",
|
||||
"thread_closed_alert": "Chủ đề này đã đóng"
|
||||
}
|
||||
@@ -48,5 +48,16 @@
|
||||
"name": "名字",
|
||||
"comment": "评论",
|
||||
"subject": "主题",
|
||||
"anonymous": "匿名"
|
||||
"anonymous": "匿名",
|
||||
"image": "图片",
|
||||
"video": "视频",
|
||||
"audio": "音频",
|
||||
"webpage": "网页",
|
||||
"iframe": "框架",
|
||||
"rules": "规则",
|
||||
"welcome_to_board": "欢迎来到{{board}}",
|
||||
"comment_too_long": "评论太长。 <1>点击这里</1>查看完整文本。",
|
||||
"replies_omitted": "省略了{{repliesCount}}条回复。 <1>点击这里</1>查看。",
|
||||
"replies_and_links_omitted": "省略了{{repliesCount}}条回复和{{linksCount}}个链接。 <1>点击这里</1>查看。",
|
||||
"thread_closed_alert": "该主题已关闭"
|
||||
}
|
||||
@@ -58,6 +58,7 @@ export const MobileBoardButtons = () => {
|
||||
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
|
||||
const subplebbitAddress = params?.subplebbitAddress || accountComment?.subplebbitAddress;
|
||||
const isInPostPage = isPostPageView(location.pathname, params);
|
||||
const isInCatalogView = isCatalogView(location.pathname, params);
|
||||
const isInPendingPostPage = isPendingPostView(location.pathname, params);
|
||||
|
||||
return (
|
||||
@@ -75,7 +76,7 @@ export const MobileBoardButtons = () => {
|
||||
) : (
|
||||
<>
|
||||
<OptionsButton />
|
||||
<CatalogButton address={subplebbitAddress} />
|
||||
<CatalogButton address={subplebbitAddress} isInCatalogView={isInCatalogView} />
|
||||
<SubscribeButton address={subplebbitAddress} />
|
||||
</>
|
||||
)}
|
||||
@@ -97,7 +98,7 @@ export const DesktopBoardButtons = () => {
|
||||
<hr />
|
||||
{isInPostPage || isInPendingPostPage ? (
|
||||
<>
|
||||
[<ReturnButton address={subplebbitAddress} />] [<CatalogButton address={subplebbitAddress} isInCatalogView={isInCatalogView} />] [<BottomButton />] [
|
||||
[<ReturnButton address={subplebbitAddress} />] [<CatalogButton address={subplebbitAddress} />] [<BottomButton />] [
|
||||
<OptionsButton />]
|
||||
<span className={styles.subscribeButton}>
|
||||
[<SubscribeButton address={subplebbitAddress} />]
|
||||
|
||||
@@ -74,7 +74,6 @@
|
||||
}
|
||||
|
||||
.challengeFooter button {
|
||||
margin: 5px 5px 0 0;
|
||||
cursor: pointer;
|
||||
filter: var(--filter80);
|
||||
text-transform: capitalize;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { useState } from 'react';
|
||||
import { useRef, useState } from 'react';
|
||||
import Draggable from 'react-draggable';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Challenge as ChallengeType } from '@plebbit/plebbit-react-hooks';
|
||||
import { getPublicationType } from '../../lib/utils/challenge-utils';
|
||||
import useChallenges from '../../hooks/use-challenges';
|
||||
import styles from './challenge-modal.module.css';
|
||||
import _ from 'lodash';
|
||||
|
||||
interface ChallengeProps {
|
||||
challenge: ChallengeType;
|
||||
@@ -50,16 +51,19 @@ const Challenge = ({ challenge, closeModal }: ChallengeProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
// react-draggable requires a ref to the modal node
|
||||
const nodeRef = useRef(null);
|
||||
|
||||
return (
|
||||
<Draggable handle='.challengeHandle'>
|
||||
<div className={styles.container}>
|
||||
<Draggable handle='.challengeHandle' nodeRef={nodeRef}>
|
||||
<div className={styles.container} ref={nodeRef}>
|
||||
<div className={`challengeHandle ${styles.title}`}>
|
||||
Challenge for {publicationType}
|
||||
<button className={styles.closeIcon} onClick={closeModal} title='close' />
|
||||
</div>
|
||||
<div className={styles.publication}>
|
||||
<div className={styles.name}>
|
||||
<input type='text' value={displayName || 'Anonymous'} disabled />
|
||||
<input type='text' value={displayName || _.capitalize(t('anonymous'))} disabled />
|
||||
</div>
|
||||
{title && (
|
||||
<div className={styles.subject}>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react';
|
||||
import styles from './comment-media.module.css';
|
||||
import { CommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
||||
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
|
||||
import Embed, { canEmbed } from '../embed';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import useWindowWidth from '../../hooks/use-window-width';
|
||||
import styles from './comment-media.module.css';
|
||||
import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../lib/utils/media-utils';
|
||||
import { getHostname } from '../../lib/utils/url-utils';
|
||||
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
|
||||
import useWindowWidth from '../../hooks/use-window-width';
|
||||
import Embed, { canEmbed } from '../embed';
|
||||
|
||||
interface MediaProps {
|
||||
commentMediaInfo?: CommentMediaInfo;
|
||||
@@ -129,7 +129,7 @@ const Media = ({ commentMediaInfo, isReply, setShowThumbnail }: MediaProps) => {
|
||||
<a href={url} target='_blank' rel='noopener noreferrer'>
|
||||
{url && url.length > 30 ? url.slice(0, 30) + '...' : url}
|
||||
</a>{' '}
|
||||
({type})
|
||||
({getDisplayMediaInfoType(type, t)})
|
||||
</div>
|
||||
)}
|
||||
{isMobile && (type === 'iframe' || type === 'video' || type === 'audio') && (
|
||||
@@ -144,8 +144,10 @@ const Media = ({ commentMediaInfo, isReply, setShowThumbnail }: MediaProps) => {
|
||||
};
|
||||
|
||||
const CommentMedia = ({ commentMediaInfo, isOutOfFeed, isReply, linkHeight, linkWidth, showThumbnail, setShowThumbnail }: MediaProps) => {
|
||||
const { t } = useTranslation();
|
||||
const isMobile = useWindowWidth() < 640;
|
||||
const { type, url } = commentMediaInfo || {};
|
||||
|
||||
return (
|
||||
<span className={styles.content}>
|
||||
<span className={`${showThumbnail ? styles.show : styles.hide} ${styles.thumbnail}`}>
|
||||
@@ -160,7 +162,7 @@ const CommentMedia = ({ commentMediaInfo, isOutOfFeed, isReply, linkHeight, link
|
||||
setShowThumbnail={setShowThumbnail}
|
||||
/>
|
||||
)}
|
||||
{isMobile && type && <div className={styles.fileInfo}>{type}</div>}
|
||||
{isMobile && type && <div className={styles.fileInfo}>{getDisplayMediaInfoType(type, t)}</div>}
|
||||
</span>
|
||||
{!showThumbnail && <Media commentMediaInfo={commentMediaInfo} isReply={isReply} setShowThumbnail={setShowThumbnail} />}
|
||||
</span>
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
.ellipsis {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.ellipsis:after {
|
||||
content: '...';
|
||||
position: absolute;
|
||||
-webkit-animation: ellipsis steps(4, end) 1200ms infinite;
|
||||
animation: ellipsis steps(4, end) 1500ms infinite;
|
||||
width: 0px;
|
||||
overflow: hidden;
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
vertical-align: bottom;
|
||||
-webkit-animation: ellipsis steps(4,end) 1500ms infinite;
|
||||
animation: ellipsis steps(4,end) 1500ms infinite;
|
||||
content: "\2026"; /* ascii code for the ellipsis character */
|
||||
width: 0px;
|
||||
}
|
||||
|
||||
@keyframes ellipsis {
|
||||
to {
|
||||
width: 1.3em;
|
||||
width: 1.25em;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes ellipsis {
|
||||
to {
|
||||
width: 1.3em;
|
||||
width: 1.25em;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useState } from 'react';
|
||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Role, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { Role, useAccount, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
||||
import { getFormattedDate } from '../../lib/utils/time-utils';
|
||||
@@ -11,10 +11,12 @@ import useReplies from '../../hooks/use-replies';
|
||||
import useStateString from '../../hooks/use-state-string';
|
||||
import useWindowWidth from '../../hooks/use-window-width';
|
||||
import styles from './post.module.css';
|
||||
import LoadingEllipsis from '../loading-ellipsis';
|
||||
import Markdown from '../markdown';
|
||||
import CommentMedia from '../comment-media';
|
||||
import { canEmbed } from '../embed';
|
||||
import LoadingEllipsis from '../loading-ellipsis';
|
||||
import Markdown from '../markdown';
|
||||
import _ from 'lodash';
|
||||
import { getDisplayMediaInfoType } from '../../lib/utils/media-utils';
|
||||
|
||||
interface PostProps {
|
||||
index?: number;
|
||||
@@ -22,9 +24,10 @@ interface PostProps {
|
||||
reply?: any;
|
||||
roles?: Role[];
|
||||
showAllReplies?: boolean;
|
||||
openReplyModal?: (cid: string) => void;
|
||||
}
|
||||
|
||||
const PostDesktop = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
const PostDesktop = ({ openReplyModal, post, roles, showAllReplies }: PostProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { author, cid, content, link, linkHeight, linkWidth, locked, pinned, postCid, replyCount, shortCid, state, subplebbitAddress, timestamp, title } = post || {};
|
||||
const { address, displayName, shortAddress } = author || {};
|
||||
@@ -46,13 +49,17 @@ const PostDesktop = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
const [showThumbnail, setShowThumbnail] = useState(true);
|
||||
|
||||
const replies = useReplies(post);
|
||||
const visibleLinkCount = useCountLinksInReplies(post, 5);
|
||||
const totalLinkCount = useCountLinksInReplies(post);
|
||||
const visiblelinksCount = useCountLinksInReplies(post, 5);
|
||||
const totallinksCount = useCountLinksInReplies(post);
|
||||
const repliesCount = pinned ? replyCount : replyCount - 5;
|
||||
const linkCount = pinned ? totalLinkCount : totalLinkCount - visibleLinkCount;
|
||||
const linksCount = pinned ? totallinksCount : totallinksCount - visiblelinksCount;
|
||||
|
||||
const [menuBtnRotated, setMenuBtnRotated] = useState(false);
|
||||
|
||||
// pending reply by account is not yet published
|
||||
const account = useAccount();
|
||||
const accountShortAddress = account?.author?.shortAddress;
|
||||
|
||||
return (
|
||||
<div className={styles.postDesktop}>
|
||||
<div className={styles.hrWrapper}>
|
||||
@@ -70,7 +77,7 @@ const PostDesktop = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
<a href={url} target='_blank' rel='noopener noreferrer'>
|
||||
{url.length > 30 ? url.slice(0, 30) + '...' : url}
|
||||
</a>{' '}
|
||||
({type})
|
||||
({type && getDisplayMediaInfoType(type, t)})
|
||||
{!showThumbnail && (type === 'iframe' || type === 'video' || type === 'audio') && (
|
||||
<span>
|
||||
{' '}
|
||||
@@ -112,10 +119,10 @@ const PostDesktop = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
{title && <span className={styles.subject}>{displayTitle} </span>}
|
||||
<span className={styles.nameBlock}>
|
||||
<span className={`${styles.name} ${(isDescription || isRules || authorRole) && styles.capcodeMod}`}>
|
||||
{displayName || 'Anonymous'}
|
||||
{displayName || _.capitalize(t('anonymous'))}
|
||||
{authorRole && ` ## Board ${authorRole}`}{' '}
|
||||
</span>
|
||||
{!(isDescription || isRules) && <span className={styles.userAddress}>(u/{shortAddress}) </span>}
|
||||
{!(isDescription || isRules) && <span className={styles.userAddress}>(u/{shortAddress || accountShortAddress}) </span>}
|
||||
</span>
|
||||
<span className={styles.dateTime}>
|
||||
{getFormattedDate(timestamp)}
|
||||
@@ -124,13 +131,13 @@ const PostDesktop = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
<span className={styles.postNum}>
|
||||
{!(isDescription || isRules) && (
|
||||
<span className={styles.postNumLink}>
|
||||
<Link to={`/p/${subplebbitAddress}/${cid}`} className={styles.linkToPost} title={t('link_to_post')} onClick={(e) => !cid && e.preventDefault()}>
|
||||
<Link to={`/p/${subplebbitAddress}/c/${cid}`} className={styles.linkToPost} title={t('link_to_post')} onClick={(e) => !cid && e.preventDefault()}>
|
||||
c/
|
||||
</Link>
|
||||
{!cid ? (
|
||||
<span className={styles.pendingCid}>{state === 'failed' ? 'Failed' : 'Pending'}</span>
|
||||
) : (
|
||||
<span className={styles.replyToPost} title={t('reply_to_post')}>
|
||||
<span className={styles.replyToPost} title={t('reply_to_post')} onClick={() => openReplyModal && openReplyModal(cid)}>
|
||||
{shortCid}
|
||||
</span>
|
||||
)}
|
||||
@@ -168,7 +175,7 @@ const PostDesktop = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
{content.length > 1000 && !isInPostPage && (
|
||||
<span className={styles.abbr}>
|
||||
<br />
|
||||
Comment too long. <Link to={`/p/${subplebbitAddress}/c/${cid}`}>Click here</Link> to view the full text.
|
||||
<Trans i18nKey={'comment_too_long'} shouldUnescape={true} components={{ 1: <Link to={`/p/${subplebbitAddress}/c/${cid}`} /> }} />
|
||||
</span>
|
||||
)}
|
||||
</blockquote>
|
||||
@@ -178,24 +185,30 @@ const PostDesktop = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
<span className={styles.expandButtonWrapper}>
|
||||
<span className={styles.expandButton} />
|
||||
</span>
|
||||
<span>
|
||||
{repliesCount} replies {linkCount > 0 && `and ${linkCount} links`} omitted.{' '}
|
||||
</span>
|
||||
<Link to={`/p/${subplebbitAddress}/c/${cid}`}>Click here</Link> to view.
|
||||
{linksCount > 0 ? (
|
||||
<Trans
|
||||
i18nKey={'replies_and_links_omitted'}
|
||||
shouldUnescape={true}
|
||||
components={{ 1: <Link to={`/p/${subplebbitAddress}/c/${cid}`} /> }}
|
||||
values={{ repliesCount, linksCount }}
|
||||
/>
|
||||
) : (
|
||||
<Trans i18nKey={'replies_omitted'} shouldUnescape={true} components={{ 1: <Link to={`/p/${subplebbitAddress}/c/${cid}`} /> }} values={{ repliesCount }} />
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
{!(pinned && !isInPostPage) &&
|
||||
replies &&
|
||||
(showAllReplies ? replies : replies.slice(-5)).map((reply, index) => (
|
||||
<div key={reply.cid} className={styles.replyContainer}>
|
||||
<ReplyDesktop index={index} reply={reply} roles={roles} />
|
||||
<div key={index} className={styles.replyContainer}>
|
||||
<ReplyDesktop index={index} reply={reply} roles={roles} openReplyModal={openReplyModal} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ReplyDesktop = ({ reply, roles }: PostProps) => {
|
||||
const ReplyDesktop = ({ reply, roles, openReplyModal }: PostProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { author, cid, content, link, linkHeight, linkWidth, parentCid, pinned, postCid, shortCid, state, subplebbitAddress, timestamp } = reply || {};
|
||||
const { address, displayName, shortAddress } = author || {};
|
||||
@@ -216,6 +229,10 @@ const ReplyDesktop = ({ reply, roles }: PostProps) => {
|
||||
<div className={`${styles.stateString} ${styles.ellipsis}`}>{stateString !== 'Failed' ? <LoadingEllipsis string={stateString} /> : stateString}</div>
|
||||
);
|
||||
|
||||
// pending reply by account is not yet published
|
||||
const account = useAccount();
|
||||
const accountShortAddress = account?.author?.shortAddress;
|
||||
|
||||
return (
|
||||
<div className={styles.replyDesktop}>
|
||||
<div className={styles.sideArrows}>{'>>'}</div>
|
||||
@@ -226,21 +243,21 @@ const ReplyDesktop = ({ reply, roles }: PostProps) => {
|
||||
</span>
|
||||
<span className={styles.nameBlock}>
|
||||
<span className={`${styles.name} ${authorRole && styles.capcodeMod}`}>
|
||||
{displayName || 'Anonymous'}
|
||||
{displayName || _.capitalize(t('anonymous'))}
|
||||
{authorRole && ` ## Board ${authorRole}`}{' '}
|
||||
</span>
|
||||
<span className={styles.userAddress}>(u/{shortAddress}) </span>
|
||||
<span className={styles.userAddress}>(u/{shortAddress || accountShortAddress}) </span>
|
||||
</span>
|
||||
<span className={styles.dateTime}>{getFormattedDate(timestamp)} </span>
|
||||
<span className={styles.postNum}>
|
||||
<span className={styles.postNumLink}>
|
||||
<Link to={`/p/${subplebbitAddress}/${cid}`} className={styles.linkToPost} title={t('link_to_post')} onClick={(e) => !cid && e.preventDefault()}>
|
||||
<Link to={`/p/${subplebbitAddress}/c/${cid}`} className={styles.linkToPost} title={t('link_to_post')} onClick={(e) => !cid && e.preventDefault()}>
|
||||
c/
|
||||
</Link>
|
||||
{!cid ? (
|
||||
<span className={styles.pendingCid}>{state === 'failed' ? 'Failed' : 'Pending'}</span>
|
||||
) : (
|
||||
<span className={styles.replyToPost} title={t('reply_to_post')}>
|
||||
<span className={styles.replyToPost} title={t('reply_to_post')} onClick={() => openReplyModal && openReplyModal(cid)}>
|
||||
{shortCid}
|
||||
</span>
|
||||
)}
|
||||
@@ -268,7 +285,8 @@ const ReplyDesktop = ({ reply, roles }: PostProps) => {
|
||||
{t('link')}:{' '}
|
||||
<a href={link} target='_blank' rel='noopener noreferrer'>
|
||||
{link.length > 30 ? link?.slice(0, 30) + '...' : link}
|
||||
</a>
|
||||
</a>{' '}
|
||||
({type && getDisplayMediaInfoType(type, t)})
|
||||
{!showThumbnail && (type === 'iframe' || type === 'video' || type === 'audio') && (
|
||||
<span>
|
||||
{' '}
|
||||
@@ -313,7 +331,7 @@ const ReplyDesktop = ({ reply, roles }: PostProps) => {
|
||||
</>
|
||||
)}
|
||||
<Markdown content={content} />
|
||||
{!cid && (
|
||||
{!cid && state === 'pending' && (
|
||||
<>
|
||||
<br />
|
||||
{loadingString}
|
||||
@@ -326,7 +344,7 @@ const ReplyDesktop = ({ reply, roles }: PostProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const PostMobile = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
const PostMobile = ({ openReplyModal, post, roles, showAllReplies }: PostProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { author, cid, content, link, linkHeight, linkWidth, locked, pinned, replyCount, shortCid, state, subplebbitAddress, timestamp, title } = post || {};
|
||||
const { address, displayName, shortAddress } = author || {};
|
||||
@@ -338,7 +356,7 @@ const PostMobile = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
const location = useLocation();
|
||||
const isInPostPage = isPostPageView(location.pathname, params);
|
||||
|
||||
const linkCount = useCountLinksInReplies(post);
|
||||
const linksCount = useCountLinksInReplies(post);
|
||||
const displayTitle = title && title.length > 30 ? title?.slice(0, 30) + '(...)' : title;
|
||||
const displayContent = content && !isInPostPage && content.length > 1000 ? content?.slice(0, 1000) : content;
|
||||
|
||||
@@ -348,6 +366,10 @@ const PostMobile = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
|
||||
const replies = useReplies(post);
|
||||
|
||||
// pending reply by account is not yet published
|
||||
const account = useAccount();
|
||||
const accountShortAddress = account?.author?.shortAddress;
|
||||
|
||||
return (
|
||||
<div className={styles.postMobile}>
|
||||
<div className={styles.hrWrapper}>
|
||||
@@ -362,10 +384,10 @@ const PostMobile = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
</span>
|
||||
<span className={styles.nameBlock}>
|
||||
<span className={`${styles.name} ${(isDescription || isRules || authorRole) && styles.capcodeMod}`}>
|
||||
{displayName || 'Anonymous'}
|
||||
{displayName || _.capitalize(t('anonymous'))}
|
||||
{authorRole && ` ## Board ${authorRole}`}{' '}
|
||||
</span>
|
||||
{!(isDescription || isRules) && <span className={styles.address}>(u/{shortAddress || address?.slice(0, 12) + '...'})</span>}
|
||||
{!(isDescription || isRules) && <span className={styles.address}>(u/{shortAddress || accountShortAddress})</span>}
|
||||
{pinned && (
|
||||
<span className={styles.stickyIconWrapper}>
|
||||
<img src='assets/icons/sticky.gif' alt='' className={styles.stickyIcon} title={t('sticky')} />
|
||||
@@ -393,7 +415,7 @@ const PostMobile = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
{!cid ? (
|
||||
<span className={styles.pendingCid}>{state === 'failed' ? 'Failed' : 'Pending'}</span>
|
||||
) : (
|
||||
<span className={styles.replyToPost} title={t('reply_to_post')}>
|
||||
<span className={styles.replyToPost} title={t('reply_to_post')} onClick={() => openReplyModal && openReplyModal(cid)}>
|
||||
{shortCid}
|
||||
</span>
|
||||
)}
|
||||
@@ -418,7 +440,7 @@ const PostMobile = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
{content.length > 1000 && !isInPostPage && (
|
||||
<span className={styles.abbr}>
|
||||
<br />
|
||||
Comment too long. <Link to={`/p/${subplebbitAddress}/c/${cid}`}>Click here</Link> to view the full text.
|
||||
<Trans i18nKey={'comment_too_long'} shouldUnescape={true} components={{ 1: <Link to={`/p/${subplebbitAddress}/c/${cid}`} /> }} />
|
||||
</span>
|
||||
)}
|
||||
</blockquote>
|
||||
@@ -428,7 +450,7 @@ const PostMobile = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
<div className={styles.postLink}>
|
||||
<span className={styles.info}>
|
||||
{replyCount > 0 && `${replyCount} Replies`}
|
||||
{linkCount > 0 && ` / ${linkCount} Links`}
|
||||
{linksCount > 0 && ` / ${linksCount} Links`}
|
||||
</span>
|
||||
<Link to={`/p/${subplebbitAddress}/${isDescription ? 'description' : isRules ? 'rules' : `c/${cid}`}`} className='button'>
|
||||
{t('view_thread')}
|
||||
@@ -440,7 +462,7 @@ const PostMobile = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
replies &&
|
||||
(showAllReplies ? replies : replies.slice(-5)).map((reply, index) => (
|
||||
<div key={reply.cid} className={styles.replyContainer}>
|
||||
<ReplyMobile index={index} reply={reply} roles={roles} />
|
||||
<ReplyMobile index={index} reply={reply} roles={roles} openReplyModal={openReplyModal} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
@@ -448,7 +470,7 @@ const PostMobile = ({ post, roles, showAllReplies }: PostProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const ReplyMobile = ({ reply, roles }: PostProps) => {
|
||||
const ReplyMobile = ({ reply, roles, openReplyModal }: PostProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { author, content, cid, link, linkHeight, linkWidth, parentCid, pinned, postCid, shortCid, state, subplebbitAddress, timestamp } = reply || {};
|
||||
const { address, displayName, shortAddress } = author || {};
|
||||
@@ -460,6 +482,15 @@ const ReplyMobile = ({ reply, roles }: PostProps) => {
|
||||
|
||||
const isReplyingToReply = postCid !== parentCid;
|
||||
|
||||
// pending reply by account is not yet published
|
||||
const account = useAccount();
|
||||
const accountShortAddress = account?.author?.shortAddress;
|
||||
|
||||
const stateString = useStateString(reply);
|
||||
const loadingString = stateString && (
|
||||
<div className={`${styles.stateString} ${styles.ellipsis}`}>{stateString !== 'Failed' ? <LoadingEllipsis string={stateString} /> : stateString}</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className={styles.replyMobile}>
|
||||
<div className={styles.reply}>
|
||||
@@ -468,10 +499,10 @@ const ReplyMobile = ({ reply, roles }: PostProps) => {
|
||||
<span className={styles.postMenuBtn}>...</span>
|
||||
<span className={styles.nameBlock}>
|
||||
<span className={`${styles.name} ${authorRole && styles.capcodeMod}`}>
|
||||
{displayName || 'Anonymous'}
|
||||
{displayName || _.capitalize(t('anonymous'))}
|
||||
{authorRole && ` ## Board ${authorRole}`}{' '}
|
||||
</span>
|
||||
<span className={styles.address}>(u/{shortAddress})</span>
|
||||
<span className={styles.address}>(u/{shortAddress || accountShortAddress})</span>
|
||||
{pinned && (
|
||||
<span className={styles.stickyIconWrapper}>
|
||||
<img src='assets/icons/sticky.gif' alt='' className={styles.stickyIcon} title={t('sticky')} />
|
||||
@@ -488,7 +519,7 @@ const ReplyMobile = ({ reply, roles }: PostProps) => {
|
||||
{!cid ? (
|
||||
<span className={styles.pendingCid}>{state === 'failed' ? 'Failed' : 'Pending'}</span>
|
||||
) : (
|
||||
<span className={styles.replyToPost} title={t('reply_to_post')}>
|
||||
<span className={styles.replyToPost} title={t('reply_to_post')} onClick={() => openReplyModal && openReplyModal(cid)}>
|
||||
{shortCid}
|
||||
</span>
|
||||
)}
|
||||
@@ -515,6 +546,12 @@ const ReplyMobile = ({ reply, roles }: PostProps) => {
|
||||
</>
|
||||
)}
|
||||
<Markdown content={content} />
|
||||
{!cid && state === 'pending' && (
|
||||
<>
|
||||
<br />
|
||||
{loadingString}
|
||||
</>
|
||||
)}
|
||||
</blockquote>
|
||||
)}
|
||||
</div>
|
||||
@@ -523,16 +560,17 @@ const ReplyMobile = ({ reply, roles }: PostProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const Post = ({ post, showAllReplies = false }: PostProps) => {
|
||||
const Post = ({ post, showAllReplies = false, openReplyModal }: PostProps) => {
|
||||
const subplebbit = useSubplebbit({ subplebbitAddress: post?.subplebbitAddress });
|
||||
const isMobile = useWindowWidth() < 640;
|
||||
|
||||
return (
|
||||
<div className={styles.thread}>
|
||||
<div className={styles.postContainer}>
|
||||
{isMobile ? (
|
||||
<PostMobile post={post} roles={subplebbit?.roles} showAllReplies={showAllReplies} />
|
||||
<PostMobile post={post} roles={subplebbit?.roles} showAllReplies={showAllReplies} openReplyModal={openReplyModal} />
|
||||
) : (
|
||||
<PostDesktop post={post} roles={subplebbit?.roles} showAllReplies={showAllReplies} />
|
||||
<PostDesktop post={post} roles={subplebbit?.roles} showAllReplies={showAllReplies} openReplyModal={openReplyModal} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './reply-modal';
|
||||
@@ -0,0 +1,60 @@
|
||||
.container {
|
||||
position: fixed;
|
||||
top: calc(50% - 150px);
|
||||
left: calc(50% - 150px);
|
||||
display: block;
|
||||
padding: 2px;
|
||||
font-size: 10pt;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
z-index: 1000;
|
||||
background-color: var(--challenge-modal-background-color);
|
||||
border: var(--challenge-modal-border);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: var(--challenge-modal-title-font-size);
|
||||
text-align: center;
|
||||
margin-bottom: 1px;
|
||||
padding: 0;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
cursor: move;
|
||||
font-weight: var(--challenge-modal-title-font-weight);
|
||||
background-color: var(--challenge-modal-title-background-color);
|
||||
color: var(--challenge-modal-title-text-color);
|
||||
border: var(--challenge-modal-title-border);
|
||||
}
|
||||
|
||||
.closeIcon {
|
||||
all: unset;
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
margin-bottom: -4px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border: none;
|
||||
image-rendering: pixelated;
|
||||
background-image: var(--close-button-background-image);
|
||||
}
|
||||
|
||||
.container input, .container textarea {
|
||||
border: 1px solid #aaa;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 10pt;
|
||||
outline: medium none;
|
||||
width: 298px;
|
||||
filter: var(--filter80);
|
||||
padding: 2px;
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
|
||||
.content textarea {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.footer button {
|
||||
text-transform: capitalize;
|
||||
float: right;
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import Draggable from 'react-draggable';
|
||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||
import { useAccount } from '@plebbit/plebbit-react-hooks';
|
||||
import { isValidURL } from '../../lib/utils/url-utils';
|
||||
import useReply from '../../hooks/use-reply';
|
||||
import styles from './reply-modal.module.css';
|
||||
import _ from 'lodash';
|
||||
|
||||
interface ReplyModalProps {
|
||||
closeModal: () => void;
|
||||
parentCid: string;
|
||||
}
|
||||
|
||||
const ReplyModal = ({ closeModal, parentCid }: ReplyModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { subplebbitAddress } = useParams() as { subplebbitAddress: string };
|
||||
const { setContent, resetContent, replyIndex, publishReply } = useReply({ cid: parentCid, subplebbitAddress });
|
||||
|
||||
const account = useAccount();
|
||||
const { displayName } = account?.author || {};
|
||||
|
||||
const textRef = useRef<HTMLTextAreaElement>(null);
|
||||
const urlRef = useRef<HTMLInputElement>(null);
|
||||
|
||||
const onPublishReply = () => {
|
||||
const currentContent = textRef.current?.value || '';
|
||||
const currentUrl = urlRef.current?.value || '';
|
||||
|
||||
if (!currentContent.trim() && !currentUrl) {
|
||||
alert(`Cannot post empty comment`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (currentUrl && !isValidURL(currentUrl)) {
|
||||
alert('The provided link is not a valid URL.');
|
||||
return;
|
||||
}
|
||||
publishReply();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof replyIndex === 'number') {
|
||||
closeModal();
|
||||
resetContent();
|
||||
}
|
||||
}, [replyIndex, resetContent, closeModal]);
|
||||
|
||||
// react-draggable requires a ref to the modal node
|
||||
const nodeRef = useRef(null);
|
||||
|
||||
return (
|
||||
<Draggable handle='.replyModalHandle' nodeRef={nodeRef}>
|
||||
<div className={styles.container} ref={nodeRef}>
|
||||
<div className={`replyModalHandle ${styles.title}`}>
|
||||
Reply to c/{parentCid && Plebbit.getShortCid(parentCid)}
|
||||
<button className={styles.closeIcon} onClick={closeModal} title='close' />
|
||||
</div>
|
||||
<div className={styles.replyForm}>
|
||||
<div className={styles.name}>
|
||||
<input type='text' defaultValue={displayName} placeholder={displayName ? undefined : _.capitalize(t('anonymous'))} />
|
||||
</div>
|
||||
<div className={styles.link}>
|
||||
<input type='text' ref={urlRef} placeholder={_.capitalize(t('link'))} onChange={(e) => setContent.link(e.target.value)} />
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<textarea cols={48} rows={4} wrap='soft' ref={textRef} placeholder={_.capitalize(t('comment'))} onChange={(e) => setContent.content(e.target.value)} />
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
<button onClick={onPublishReply}>{t('reply')}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Draggable>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReplyModal;
|
||||
@@ -1,4 +1,5 @@
|
||||
import Post from '../post';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface DescriptionPostProps {
|
||||
subplebbitAddress: string | undefined;
|
||||
@@ -9,6 +10,7 @@ interface DescriptionPostProps {
|
||||
}
|
||||
|
||||
const SubplebbitDescription = ({ subplebbitAddress, createdAt, description, avatarUrl, title }: DescriptionPostProps) => {
|
||||
const { t } = useTranslation();
|
||||
const post = {
|
||||
isDescription: true,
|
||||
subplebbitAddress,
|
||||
@@ -16,7 +18,7 @@ const SubplebbitDescription = ({ subplebbitAddress, createdAt, description, avat
|
||||
author: { displayName: '## Board Mods' },
|
||||
content: description,
|
||||
link: avatarUrl,
|
||||
title: 'Welcome to ' + title,
|
||||
title: t('welcome_to_board', { board: title }),
|
||||
pinned: true,
|
||||
locked: true,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import Post from '../post';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import _ from 'lodash';
|
||||
|
||||
interface RulesPostProps {
|
||||
subplebbitAddress: string | undefined;
|
||||
@@ -7,6 +9,7 @@ interface RulesPostProps {
|
||||
}
|
||||
|
||||
const SubplebbitRules = ({ subplebbitAddress, createdAt, rules }: RulesPostProps) => {
|
||||
const { t } = useTranslation();
|
||||
const content = rules.map((rule, index) => `${index + 1}. ${rule}`).join('\n');
|
||||
const post = {
|
||||
isRules: true,
|
||||
@@ -14,7 +17,7 @@ const SubplebbitRules = ({ subplebbitAddress, createdAt, rules }: RulesPostProps
|
||||
timestamp: createdAt,
|
||||
author: { displayName: '## Board Mods' },
|
||||
content,
|
||||
title: 'Rules',
|
||||
title: _.capitalize(t('rules')),
|
||||
pinned: true,
|
||||
locked: true,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
|
||||
const useReplyModal = () => {
|
||||
const [showReplyModal, setShowReplyModal] = useState(false);
|
||||
const [activeCid, setActiveCid] = useState<string | null>(null);
|
||||
|
||||
const openReplyModal = useCallback(
|
||||
(cid: string) => {
|
||||
if (activeCid && activeCid !== cid) {
|
||||
closeModal(); // close modal if a different CID is clicked
|
||||
setActiveCid(cid);
|
||||
setShowReplyModal(true);
|
||||
} else if (!activeCid) {
|
||||
setActiveCid(cid);
|
||||
setShowReplyModal(true);
|
||||
}
|
||||
},
|
||||
[activeCid],
|
||||
);
|
||||
|
||||
const closeModal = useCallback(() => {
|
||||
setActiveCid(null);
|
||||
setShowReplyModal(false);
|
||||
}, []);
|
||||
|
||||
return { showReplyModal, activeCid, openReplyModal, closeModal };
|
||||
};
|
||||
|
||||
export default useReplyModal;
|
||||
@@ -11,6 +11,21 @@ export interface CommentMediaInfo {
|
||||
patternThumbnailUrl?: string;
|
||||
}
|
||||
|
||||
export const getDisplayMediaInfoType = (type: string, t: any) => {
|
||||
switch (type) {
|
||||
case 'image':
|
||||
return t('image');
|
||||
case 'iframe':
|
||||
return t('iframe');
|
||||
case 'video':
|
||||
return t('video');
|
||||
case 'audio':
|
||||
return t('audio');
|
||||
default:
|
||||
return t('webpage');
|
||||
}
|
||||
};
|
||||
|
||||
export const getHasThumbnail = (commentMediaInfo: CommentMediaInfo | undefined, link: string | undefined): boolean => {
|
||||
const iframeThumbnail = commentMediaInfo?.patternThumbnailUrl || commentMediaInfo?.thumbnail;
|
||||
return link &&
|
||||
|
||||
@@ -5,8 +5,10 @@ import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import styles from './board.module.css';
|
||||
import useFeedStateString from '../../hooks/use-feed-state-string';
|
||||
import useReplyModal from '../../hooks/use-reply-modal';
|
||||
import LoadingEllipsis from '../../components/loading-ellipsis';
|
||||
import Post from '../../components/post';
|
||||
import ReplyModal from '../../components/reply-modal';
|
||||
import SubplebbitDescription from '../../components/subplebbit-description';
|
||||
import SubplebbitRules from '../../components/subplebbit-rules';
|
||||
|
||||
@@ -22,6 +24,8 @@ const Board = () => {
|
||||
const subplebbit = useSubplebbit({ subplebbitAddress });
|
||||
const { createdAt, description, rules, shortAddress, state, suggested, title } = subplebbit || {};
|
||||
|
||||
const { showReplyModal, activeCid, openReplyModal, closeModal } = useReplyModal();
|
||||
|
||||
const loadingStateString = useFeedStateString(subplebbitAddresses) || t('loading');
|
||||
const loadingString = <div className={styles.stateString}>{state === 'failed' ? state : <LoadingEllipsis string={loadingStateString} />}</div>;
|
||||
|
||||
@@ -58,6 +62,7 @@ const Board = () => {
|
||||
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
{showReplyModal && activeCid && <ReplyModal closeModal={closeModal} parentCid={activeCid} />}
|
||||
{feed.length > 0 && (
|
||||
<>
|
||||
{rules && rules.length > 0 && <SubplebbitRules subplebbitAddress={subplebbitAddress} createdAt={createdAt} rules={rules} />}
|
||||
@@ -70,7 +75,12 @@ const Board = () => {
|
||||
increaseViewportBy={{ bottom: 1200, top: 1200 }}
|
||||
totalCount={feed?.length || 0}
|
||||
data={feed}
|
||||
itemContent={(index, post) => <Post index={index} post={post} />}
|
||||
itemContent={(index, post) => {
|
||||
const { deleted, locked, removed } = post || {};
|
||||
const isThreadClosed = deleted || locked || removed;
|
||||
|
||||
return <Post index={index} post={post} openReplyModal={isThreadClosed ? () => alert(t('thread_closed_alert')) : openReplyModal} />;
|
||||
}}
|
||||
useWindowScroll={true}
|
||||
components={{ Footer }}
|
||||
endReached={loadMore}
|
||||
|
||||
@@ -210,7 +210,7 @@ const Footer = () => {
|
||||
)}
|
||||
{commitRef && (
|
||||
<a href={`https://github.com/plebbit/plebchan/commit/${commitRef}`} target='_blank' rel='noopener noreferrer'>
|
||||
{commitRef.slice(0, 7)}
|
||||
#{commitRef.slice(0, 7)}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { isDescriptionView, isRulesView } from '../../lib/utils/view-utils';
|
||||
import styles from './post-page.module.css';
|
||||
import { isDescriptionView, isRulesView } from '../../lib/utils/view-utils';
|
||||
import useReplyModal from '../../hooks/use-reply-modal';
|
||||
import Post from '../../components/post';
|
||||
import ReplyModal from '../../components/reply-modal';
|
||||
import SubplebbitDescription from '../../components/subplebbit-description';
|
||||
import SubplebbitRules from '../../components/subplebbit-rules';
|
||||
|
||||
const PostPage = () => {
|
||||
const { t } = useTranslation();
|
||||
const params = useParams();
|
||||
const { commentCid, subplebbitAddress } = params;
|
||||
|
||||
@@ -18,7 +22,11 @@ const PostPage = () => {
|
||||
const isInDescriptionView = isDescriptionView(location.pathname, params);
|
||||
const isInRulesView = isRulesView(location.pathname, params);
|
||||
|
||||
const { showReplyModal, activeCid, openReplyModal, closeModal } = useReplyModal();
|
||||
|
||||
const post = useComment({ commentCid });
|
||||
const { deleted, locked, removed } = post || {};
|
||||
const isThreadClosed = deleted || locked || removed;
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
@@ -26,12 +34,13 @@ const PostPage = () => {
|
||||
|
||||
return (
|
||||
<div className={styles.content}>
|
||||
{showReplyModal && activeCid && <ReplyModal closeModal={closeModal} parentCid={activeCid} />}
|
||||
{isInDescriptionView ? (
|
||||
<SubplebbitDescription avatarUrl={suggested?.avatarUrl} createdAt={createdAt} description={description} subplebbitAddress={subplebbitAddress} title={title} />
|
||||
) : isInRulesView ? (
|
||||
<SubplebbitRules createdAt={createdAt} rules={rules} subplebbitAddress={subplebbitAddress} />
|
||||
) : (
|
||||
<Post post={post} showAllReplies={true} />
|
||||
<Post post={post} showAllReplies={true} openReplyModal={isThreadClosed ? () => alert(t('thread_closed_alert')) : openReplyModal} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user