Merge pull request #538 from plebbit/development

Development
This commit is contained in:
Tom (plebeius.eth)
2024-09-16 12:39:11 +02:00
committed by GitHub
55 changed files with 2006 additions and 1933 deletions
+3
View File
@@ -153,5 +153,8 @@
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"resolutions": {
"skia-canvas": "1.0.2"
}
}
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "GIF متحرك",
"hide_avatars": "إخفاء الصور الرمزية",
"anon_mode": "وضع مجهول",
"anon_mode_description": "يتم استخدام معرف مستخدم مختلف تلقائيًا في كل موضوع",
"anon_mode_description": "استخدم معرف مستخدم مختلف في كل موضوع",
"pending": "قيد الانتظار",
"failed": "فشل",
"add": "أضف",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "অ্যানিমেটেড GIF",
"hide_avatars": "অ্যাভাটার লুকান",
"anon_mode": "অজ্ঞাত মোড",
"anon_mode_description": "প্রতিটি থ্রেডে স্বয়ংক্রিয়ভাবে একটি ভিন্ন ব্যবহারকারী আইডি ব্যবহার করুন",
"anon_mode_description": "প্রতিটি থ্রেডে একটি ভিন্ন ব্যবহারকারী আইডি ব্যবহার করুন",
"pending": "অপেক্ষমাণ",
"failed": "ব্যর্থ",
"add": "যোগ করুন",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "Animovaný GIF",
"hide_avatars": "Skrýt avatary",
"anon_mode": "Anonymní režim",
"anon_mode_description": "Automaticky používejte jiný uživatelský ID v každém vlákně",
"anon_mode_description": "Použijte různé uživatelské ID v každém vlákně",
"pending": "Čeká na schválení",
"failed": "Nepodařilo se",
"add": "Přidat",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "Animeret GIF",
"hide_avatars": "Skjul avatarer",
"anon_mode": "Anonim tilstand",
"anon_mode_description": "Brug automatisk en anden bruger-ID i hver tråd",
"anon_mode_description": "Brug et forskelligt bruger-ID i hver tråd",
"pending": "Afventer",
"failed": "Mislykkedes",
"add": "Tilføj",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "Animiertes GIF",
"hide_avatars": "Avatare ausblenden",
"anon_mode": "Anonymer Modus",
"anon_mode_description": "Verwenden Sie automatisch eine andere Benutzer-ID in jedem Thread",
"anon_mode_description": "Verwenden Sie eine andere Benutzer-ID in jedem Thread",
"pending": "Ausstehend",
"failed": "Fehlgeschlagen",
"add": "Hinzufügen",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "Κινούμενο GIF",
"hide_avatars": "Απόκρυψη avatars",
"anon_mode": "Λειτουργία ανωνυμίας",
"anon_mode_description": "Χρησιμοποιήστε αυτόματα διαφορετικό αναγνωριστικό χρήστη σε κάθε νήμα",
"anon_mode_description": "Χρησιμοποιήστε ένα διαφορετικό αναγνωριστικό χρήστη σε κάθε θέμα",
"pending": "Σε εκκρεμότητα",
"failed": "Απέτυχε",
"add": "Προσθήκη",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "Animated GIF",
"hide_avatars": "Hide avatars",
"anon_mode": "Anon mode",
"anon_mode_description": "Automatically use a different user ID in each thread",
"anon_mode_description": "Use a different user ID in each thread",
"pending": "Pending",
"failed": "Failed",
"add": "Add",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "GIF Animado",
"hide_avatars": "Ocultar avatares",
"anon_mode": "Modo anónimo",
"anon_mode_description": "Utiliza automáticamente un ID de usuario diferente en cada hilo",
"anon_mode_description": "Usa un ID de usuario diferente en cada hilo",
"pending": "Pendiente",
"failed": "Fallido",
"add": "Añadir",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "GIF متحرک",
"hide_avatars": "پنهان کردن آواتارها",
"anon_mode": "حالت ناشناس",
"anon_mode_description": "به طور خودکار از یک شناسه کاربری متفاوت در هر موضوع استفاده کنید",
"anon_mode_description": "در هر رشته یک شناسه کاربری مختلف استفاده کنید",
"pending": "در انتظار",
"failed": "ناموفق",
"add": "اضافه کردن",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "Animoitu GIF",
"hide_avatars": "Piilota avatarit",
"anon_mode": "Anonyymi tila",
"anon_mode_description": "Käytä automaattisesti eri käyttäjä-ID:tä jokaisessa keskustelussa",
"anon_mode_description": "Käytä eri käyttäjä-ID:tä jokaisessa ketjussa",
"pending": "Odottaa",
"failed": "Epäonnistui",
"add": "Lisää",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "Animated GIF",
"hide_avatars": "Itago ang mga avatar",
"anon_mode": "Anong mode",
"anon_mode_description": "Awtomatikong gamitin ang ibang user ID sa bawat thread",
"anon_mode_description": "Gumamit ng iba't ibang user ID sa bawat thread",
"pending": "Naghihintay",
"failed": "Nabigo",
"add": "Idagdag",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "GIF Animé",
"hide_avatars": "Masquer les avatars",
"anon_mode": "Mode anonyme",
"anon_mode_description": "Utilisez automatiquement un identifiant utilisateur différent dans chaque fil",
"anon_mode_description": "Utilisez un ID utilisateur différent dans chaque fil",
"pending": "En attente",
"failed": "Échoué",
"add": "Ajouter",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "GIF מונפש",
"hide_avatars": "הסתר את התמונות האישיות",
"anon_mode": "מצב אנונימי",
"anon_mode_description": "השתמש אוטומטית במזהה משתמש שונה בכל שרשור",
"anon_mode_description": "השתמש במזהה משתמש שונה בכל שרשור",
"pending": "ממתין",
"failed": "נכשל",
"add": "הוסף",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "एनिमेटेड GIF",
"hide_avatars": "अवतार छिपाएँ",
"anon_mode": "अनाम मोड",
"anon_mode_description": "प्रत्येक थ्रेड में स्वचालित रूप से एक अलग उपयोगकर्ता आईडी का उपयोग करें",
"anon_mode_description": "प्रत्येक थ्रेड में एक अलग उपयोगकर्ता आईडी का उपयोग करें",
"pending": "लंबित",
"failed": "असफल",
"add": "जोड़ें",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "Animált GIF",
"hide_avatars": "Avatarok elrejtése",
"anon_mode": "Anonim mód",
"anon_mode_description": "Automatikusan használjon más felhasználói azonosítót minden szálban",
"anon_mode_description": "Használjon különböző felhasználói azonosítót minden szálban",
"pending": "Függőben",
"failed": "Sikertelen",
"add": "Hozzáad",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "GIF Animasi",
"hide_avatars": "Sembunyikan avatar",
"anon_mode": "Mode anonim",
"anon_mode_description": "Secara otomatis gunakan ID pengguna yang berbeda di setiap utas",
"anon_mode_description": "Gunakan ID pengguna yang berbeda di setiap thread",
"pending": "Tertunda",
"failed": "Gagal",
"add": "Tambahkan",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "GIF Animata",
"hide_avatars": "Nascondi avatar",
"anon_mode": "Modalità anonima",
"anon_mode_description": "Usa automaticamente un ID utente diverso in ogni thread",
"anon_mode_description": "Usa un ID utente diverso per ogni thread",
"pending": "In attesa",
"failed": "Fallito",
"add": "Aggiungi",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "アニメーションGIF",
"hide_avatars": "アバターを隠す",
"anon_mode": "匿名モード",
"anon_mode_description": "各スレッドで自動的に異なるユーザーIDを使用します",
"anon_mode_description": "各スレッドで異なるユーザーIDを使用す",
"pending": "保留中",
"failed": "失敗",
"add": "追加",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "애니메이션 GIF",
"hide_avatars": "아바타 숨기기",
"anon_mode": "익명 모드",
"anon_mode_description": "각 스레드에서 자동으로 다른 사용자 ID를 사용합니다",
"anon_mode_description": "각 스레드에서 다른 사용자 ID를 사용하세요",
"pending": "대기 중",
"failed": "실패",
"add": "추가",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "एनिमेटेड GIF",
"hide_avatars": "अवतार लपवा",
"anon_mode": "अनाम मोड",
"anon_mode_description": "प्रत्येक थ्रेडमध्ये स्वयंचलितपणे वेगळा वापरकर्ता ID वापरा",
"anon_mode_description": "प्रत्येक थ्रेडमध्ये एक वेगवेगळा वापरकर्ता ID वापरा",
"pending": "प्रलंबित",
"failed": "असफल",
"add": "अॅड करा",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "Geanimeerde GIF",
"hide_avatars": "Avatars verbergen",
"anon_mode": "Anoniem modus",
"anon_mode_description": "Gebruik automatisch een ander gebruikers-ID in elk thread",
"anon_mode_description": "Gebruik een ander gebruikers-ID in elk draad",
"pending": "In afwachting",
"failed": "Mislukt",
"add": "Toevoegen",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "Animasjons GIF",
"hide_avatars": "Skjul avatarer",
"anon_mode": "Anonim modus",
"anon_mode_description": "Bruk automatisk en annen bruker-ID i hver tråd",
"anon_mode_description": "Bruk et annet bruker-ID i hver tråd",
"pending": "Venter",
"failed": "Feilet",
"add": "Legg til",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "Animowany GIF",
"hide_avatars": "Ukryj awatary",
"anon_mode": "Tryb anonimowy",
"anon_mode_description": "Automatycznie używaj innego identyfikatora użytkownika w każdym wątku",
"anon_mode_description": "Użyj innego identyfikatora użytkownika w każdym wątku",
"pending": "Oczekujące",
"failed": "Nieudane",
"add": "Dodaj",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "GIF Animado",
"hide_avatars": "Ocultar avatares",
"anon_mode": "Modo anônimo",
"anon_mode_description": "Use automaticamente um ID de usuário diferente em cada thread",
"anon_mode_description": "Use um ID de usuário diferente em cada thread",
"pending": "Pendente",
"failed": "Falhou",
"add": "Adicionar",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "GIF Animat",
"hide_avatars": "Ascunde avatarele",
"anon_mode": "Mod anonim",
"anon_mode_description": "Utilizați automat un ID de utilizator diferit în fiecare fir",
"anon_mode_description": "Utilizați un ID de utilizator diferit în fiecare fir",
"pending": "În așteptare",
"failed": "Eșuat",
"add": "Adaugă",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "Анимированный GIF",
"hide_avatars": "Скрыть аватары",
"anon_mode": "Анонимный режим",
"anon_mode_description": "Автоматически используйте другой идентификатор пользователя в каждом потоке",
"anon_mode_description": "Используйте другой идентификатор пользователя в каждом потоке",
"pending": "В ожидании",
"failed": "Не удалось",
"add": "Добавить",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "GIF i Animuar",
"hide_avatars": "Fsheh avatarët",
"anon_mode": "Mënyra anonime",
"anon_mode_description": "Përdorni automatikisht një ID përdoruesi të ndryshëm në çdo temë",
"anon_mode_description": "Përdorni një ID të ndryshme përdoruesi në çdo temë",
"pending": "Në pritje",
"failed": "Dështuar",
"add": "Shto",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "Animerad GIF",
"hide_avatars": "Dölj avatarer",
"anon_mode": "Anonymt läge",
"anon_mode_description": "Använd automatiskt ett annat användar-ID i varje tråd",
"anon_mode_description": "Använd ett annat användar-ID i varje tråd",
"pending": "Väntande",
"failed": "Misslyckades",
"add": "Lägg till",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "అనిమేటెడ్ GIF",
"hide_avatars": "అవతార్లను దాచు",
"anon_mode": "అనానిమస్ మోడ్",
"anon_mode_description": "ప్రతి థ్రెడ్లో ఆటోమేటిక్‌గా ఒక వేరు వినియోగదారుడి ID ఉపయోగించండి",
"anon_mode_description": "ప్రతి థ్రెడ్లో భిన్నమైన యూజర్ ID ఉపయోగించండి",
"pending": "అనుమతించబడాలి",
"failed": "విజయవంతం కాలేదు",
"add": "చేర్చండి",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "GIF ที่เคลื่อนไหว",
"hide_avatars": "ซ่อนอวาตาร์",
"anon_mode": "โหมดนิรนาม",
"anon_mode_description": "ใช้ ID ผู้ใช้ที่แตกต่างกันโดยอัตโนมัติในแต่ละเธรด",
"anon_mode_description": "ใช้ ID ผู้ใช้ที่แตกต่างกันในแต่ละกระทู้",
"pending": "รอดำเนินการ",
"failed": "ล้มเหลว",
"add": "เพิ่ม",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "Animasyonlu GIF",
"hide_avatars": "Avatarları gizle",
"anon_mode": "Gizli mod",
"anon_mode_description": "Her iplikte otomatik olarak farklı bir kullanıcı ID'si kullanın",
"anon_mode_description": "Her başlıkta farklı bir kullanıcı ID'si kullanın",
"pending": "Beklemede",
"failed": "Başarısız",
"add": "Ekle",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "Анімований GIF",
"hide_avatars": "Сховати аватари",
"anon_mode": "Режим анонімності",
"anon_mode_description": "Автоматично використовуйте інший ідентифікатор користувача в кожній гілці",
"anon_mode_description": "Використовуйте інший ідентифікатор користувача в кожній темі",
"pending": "Очікує",
"failed": "Не вдалося",
"add": "Додати",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "اینیمیٹڈ GIF",
"hide_avatars": "اوٹار چھپائیں",
"anon_mode": "خفیہ موڈ",
"anon_mode_description": "ہر تھریڈ میں خود بخود مختلف صارف آئی ڈی کا استعمال کریں",
"anon_mode_description": "ہر تھریڈ میں ایک مختلف صارف آئی ڈی استعمال کریں",
"pending": "منتظر",
"failed": "ناکام",
"add": "شامل کریں",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "GIF Hoạt Hình",
"hide_avatars": "Ẩn avatar",
"anon_mode": "Chế độ ẩn danh",
"anon_mode_description": "Tự động sử dụng ID người dùng khác nhau trong mỗi chủ đề",
"anon_mode_description": "Sử dụng ID người dùng khác nhau trong từng chủ đề",
"pending": "Đang chờ",
"failed": "Thất bại",
"add": "Thêm",
+1 -1
View File
@@ -224,7 +224,7 @@
"animated_gif": "动画GIF",
"hide_avatars": "隐藏头像",
"anon_mode": "匿名模式",
"anon_mode_description": "在每个线程中自动使用不同的用户 ID",
"anon_mode_description": "在每个线程中使用不同的用户 ID",
"pending": "待处理",
"failed": "失败",
"add": "添加",
@@ -29,10 +29,6 @@
width: 10px;
}
.disabledButton {
opacity: 0.5 !important;
}
.disabledButton:hover {
cursor: not-allowed !important;
}
@@ -1,7 +1,7 @@
import { useTranslation } from 'react-i18next';
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
import { useAccountComment, useComment, useSubscribe } from '@plebbit/plebbit-react-hooks';
import { isAllView, isCatalogView, isDescriptionView, isPendingPostView, isPostPageView, isRulesView, isSubscriptionsView } from '../../lib/utils/view-utils';
import { isAllView, isCatalogView, isDescriptionView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
import useCatalogStyleStore from '../../stores/use-catalog-style-store';
import useFeedResetStore from '../../stores/use-feed-reset-store';
import useSortingStore from '../../stores/use-sorting-store';
@@ -96,11 +96,11 @@ const UpdateButton = () => {
<>
{/* TODO: Implement update button once available in API */}
{isMobile ? (
<button className={`button ${styles.disabledButton}`} disabled>
<button className={`button ${styles.disabledButton}`} onClick={() => alert('Coming soon')}>
{t('update')}
</button>
) : (
<button className={`button ${styles.disabledButton}`} disabled>
<button className={`button ${styles.disabledButton}`} onClick={() => alert('Coming soon')}>
{t('update')}
</button>
)}
@@ -293,19 +293,18 @@ const PostPageStats = () => {
const params = useParams();
const location = useLocation();
const isInDescriptionView = isDescriptionView(location.pathname, params);
const isInRulesView = isRulesView(location.pathname, params);
const comment = useComment({ commentCid: params?.commentCid });
const { closed, pinned, replyCount } = comment || {};
const linkCount = useCountLinksInReplies(comment);
const displayReplyCount = replyCount !== undefined ? replyCount.toString() : '?';
const replyCountTooltip = replyCount !== undefined ? _.capitalize(t('replies')) : t('loading');
const displayReplyCount = replyCount !== undefined ? replyCount.toString() : isInDescriptionView ? 1 : '?';
const replyCountTooltip = replyCount !== undefined || isInDescriptionView ? _.capitalize(t('replies')) : t('loading');
return (
<span>
{(pinned || isInDescriptionView || isInRulesView) && `${_.capitalize(t('sticky'))} / `}
{(closed || isInDescriptionView || isInRulesView) && `${_.capitalize(t('closed'))} / `}
{(pinned || isInDescriptionView) && `${_.capitalize(t('sticky'))} / `}
{(closed || isInDescriptionView) && `${_.capitalize(t('closed'))} / `}
<Tooltip children={displayReplyCount} content={replyCountTooltip} /> / <Tooltip children={linkCount?.toString()} content={_.capitalize(t('links'))} />
</span>
);
@@ -379,7 +378,7 @@ export const DesktopBoardButtons = () => {
{(isInAllView || isInSubscriptionsView) && (
<TimeFilter isInAllView={isInAllView} isInCatalogView={isInCatalogView} isInSubscriptionsView={isInSubscriptionsView} />
)}
{(isInCatalogView || isInAllView || isInSubscriptionsView) && (
{isInCatalogView && (
<>
[
<CatalogFilters />]
+19 -5
View File
@@ -1,7 +1,7 @@
import { useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Link, useLocation, useParams } from 'react-router-dom';
import { Comment, useAccount, useAuthorAvatar, useComment, useEditedComment } from '@plebbit/plebbit-react-hooks';
import { Comment, useAccount, useAuthorAvatar, useComment, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
import styles from '../../views/post/post.module.css';
import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
@@ -274,7 +274,7 @@ const PostMedia = ({ post }: PostProps) => {
};
const PostMessage = ({ post }: PostProps) => {
const { cid, content, deleted, edit, original, parentCid, postCid, reason, removed, state } = post || {};
const { cid, content, deleted, edit, isRules, original, parentCid, postCid, reason, removed, state } = post || {};
// TODO: commentAuthor is not yet available outside of editedComment, wait for API to be updated
// const banned = !!post?.commentAuthor?.banExpiresAt;
const { t } = useTranslation();
@@ -297,7 +297,7 @@ const PostMessage = ({ post }: PostProps) => {
);
return (
<blockquote className={styles.postMessage}>
<blockquote className={`${styles.postMessage} ${isRules && styles.rulesMessage}`}>
{isReply && !(removed || deleted) && state !== 'failed' && isReplyingToReply && <ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={quotelinkReply} />}
{removed ? (
reason ? (
@@ -433,6 +433,17 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
const stateString = useStateString(post);
const subplebbit = useSubplebbit({ subplebbitAddress });
const subplebbitRulesReply = {
isRules: true,
subplebbitAddress,
timestamp: subplebbit?.createdAt,
author: { displayName: `## ${t('board_mods')}` },
content: `${subplebbit?.rules?.map((rule: string, index: number) => `${index + 1}. ${rule}`).join('\n')}`,
replyCount: 0,
};
return (
<div className={styles.postDesktop}>
{showReplies ? (
@@ -477,8 +488,6 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
{!isHidden &&
!(pinned && !isInPostPageView && !showOmittedReplies[cid]) &&
!isInPendingPostView &&
!isDescription &&
!isRules &&
replies &&
showReplies &&
(showAllReplies || showOmittedReplies[cid] ? replies : replies.slice(-5)).map((reply, index) => (
@@ -486,6 +495,11 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
<Reply openReplyModal={openReplyModal} reply={reply} roles={roles} postReplyCount={replyCount} />
</div>
))}
{isDescription && subplebbit?.rules && subplebbit?.rules.length > 0 && (
<div className={styles.replyContainer}>
<Reply reply={subplebbitRulesReply} />
</div>
)}
</div>
{!isInPendingPostView &&
(stateString && stateString !== 'Failed' && state !== 'succeeded' ? (
+19 -5
View File
@@ -1,7 +1,7 @@
import { useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { Link, useLocation, useParams } from 'react-router-dom';
import { Comment, useAccount, useAuthorAvatar, useComment, useEditedComment } from '@plebbit/plebbit-react-hooks';
import { Comment, useAccount, useAuthorAvatar, useComment, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
import styles from '../../views/post/post.module.css';
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
@@ -200,7 +200,7 @@ const ReplyBacklinks = ({ post }: PostProps) => {
const PostMessageMobile = ({ post }: PostProps) => {
const { t } = useTranslation();
const { cid, content, deleted, edit, original, parentCid, postCid, reason, removed, state } = post || {};
const { cid, content, deleted, edit, isRules, original, parentCid, postCid, reason, removed, state } = post || {};
// TODO: commentAuthor is not available outside of editedComment, update when available
// const banned = !!post?.commentAuthor?.banExpiresAt;
const [showOriginal, setShowOriginal] = useState(false);
@@ -223,7 +223,7 @@ const PostMessageMobile = ({ post }: PostProps) => {
);
return (
<blockquote className={`${styles.postMessage} ${!isReply && styles.clampLines}`}>
<blockquote className={`${styles.postMessage} ${!isReply && styles.clampLines} ${isRules && styles.rulesMessage}`}>
{isReply && !(removed || deleted) && state !== 'failed' && isReplyingToReply && <ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={quotelinkReply} />}
{removed ? (
reason ? (
@@ -353,6 +353,17 @@ const PostMobile = ({ openReplyModal, post, roles, showAllReplies, showReplies =
const stateString = useStateString(post);
const subplebbit = useSubplebbit({ subplebbitAddress });
const showRules = isDescription && subplebbit?.rules && subplebbit?.rules.length > 0;
const subplebbitRulesReply = {
isRules: true,
subplebbitAddress,
timestamp: subplebbit?.createdAt,
author: { displayName: `## ${t('board_mods')}` },
content: `${subplebbit?.rules?.map((rule: string, index: number) => `${index + 1}. ${rule}`).join('\n')}`,
replyCount: 0,
};
return (
<>
{hidden && !isInPostPageView ? (
@@ -394,8 +405,6 @@ const PostMobile = ({ openReplyModal, post, roles, showAllReplies, showReplies =
</div>
{!(pinned && !isInPostView) &&
!isInPendingPostView &&
!isDescription &&
!isRules &&
replies &&
showReplies &&
(showAllReplies ? replies : replies.slice(-5)).map((reply, index) => (
@@ -403,6 +412,11 @@ const PostMobile = ({ openReplyModal, post, roles, showAllReplies, showReplies =
<Reply openReplyModal={openReplyModal} postReplyCount={replyCount} reply={reply} roles={roles} />
</div>
))}
{showRules && (
<div className={styles.replyContainer}>
<Reply reply={subplebbitRulesReply} />
</div>
)}
</div>
{!isInPendingPostView &&
(stateString && stateString !== 'Failed' && state !== 'succeeded' ? (
@@ -5,6 +5,8 @@ import useTheme from '../../../hooks/use-theme';
import packageJson from '../../../../package.json';
import styles from './interface-settings.module.css';
import _ from 'lodash';
import useInterfaceSettingsStore from '../../../stores/use-interface-settings-store';
import useCatalogFiltersStore from '../../../stores/use-catalog-filters-store';
const commitRef = process.env.REACT_APP_COMMIT_REF;
const isElectron = window.isElectron === true;
@@ -110,6 +112,8 @@ const InterfaceLanguage = () => {
const InterfaceSettings = () => {
const { t } = useTranslation();
const { hideAvatars, setHideAvatars } = useAvatarVisibilityStore();
const { hideGoreBoards, setHideGoreBoards, hideAdultBoards, setHideAdultBoards, hideThreadsWithoutImages, setHideThreadsWithoutImages } = useInterfaceSettingsStore();
const { setShowTextOnlyThreads } = useCatalogFiltersStore();
const handleHideAvatarsChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setHideAvatars(e.target.checked);
@@ -142,6 +146,29 @@ const InterfaceSettings = () => {
<input type='checkbox' checked={hideAvatars} onChange={handleHideAvatarsChange} /> {_.capitalize(t('hide_avatars'))}
</label>
</div>
<div className={styles.setting}>
<label>
<input type='checkbox' checked={hideGoreBoards} onChange={(e) => setHideGoreBoards(e.target.checked)} /> {_.capitalize(t('hide_gore_boards'))}
</label>
</div>
<div className={styles.setting}>
<label>
<input type='checkbox' checked={hideAdultBoards} onChange={(e) => setHideAdultBoards(e.target.checked)} /> {_.capitalize(t('hide_adult_boards'))}
</label>
</div>
<div className={styles.setting}>
<label>
<input
type='checkbox'
checked={hideThreadsWithoutImages}
onChange={(e) => {
setHideThreadsWithoutImages(e.target.checked);
setShowTextOnlyThreads(!e.target.checked);
}}
/>
{_.capitalize(t('hide_threads_without_images'))}
</label>
</div>
</div>
);
};
@@ -26,7 +26,7 @@ const SubplebbitDescription = ({ avatarUrl, createdAt, description, shortAddress
author: { displayName: `## ${t('board_mods')}` },
content: isInAllView ? multisubMetadata?.description : description,
link: avatarUrl,
replyCount: 0,
replyCount: 1,
title: t('welcome_to_board', {
board: isInAllView ? multisubMetadata?.title : title || `p/${shortAddress}`,
interpolation: { escapeValue: false },
-1
View File
@@ -32,7 +32,6 @@
}
.disabledButton {
opacity: 0.5;
cursor: not-allowed !important;
}
+2
View File
@@ -91,6 +91,7 @@ const TopBarDesktop = () => {
to='boards/create'
onClick={(e) => {
e.preventDefault();
alert('Coming soon');
}}
>
Create
@@ -101,6 +102,7 @@ const TopBarDesktop = () => {
to='boards'
onClick={(e) => {
e.preventDefault();
alert('Coming soon');
}}
>
Vote
+8 -23
View File
@@ -2,17 +2,17 @@ import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams, useLocation } from 'react-router-dom';
import { useAccountComments, Subplebbit } from '@plebbit/plebbit-react-hooks';
import useInterfaceSettingsStore from '../stores/use-interface-settings-store';
import { getCommentMediaInfo, getHasThumbnail } from '../lib/utils/media-utils';
import { isAllView } from '../lib/utils/view-utils';
import { useMultisubMetadata } from './use-default-subplebbits';
import useCatalogFiltersStore from '../stores/use-catalog-filters-store';
import _ from 'lodash';
import { getCommentMediaInfo, getHasThumbnail } from '../lib/utils/media-utils';
const useCatalogFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolean, subplebbit: Subplebbit) => {
const { t } = useTranslation();
const { address, createdAt, description, rules, shortAddress, suggested, title } = subplebbit || {};
const { avatarUrl } = suggested || {};
const { showTextOnlyThreads } = useCatalogFiltersStore();
const { hideThreadsWithoutImages } = useInterfaceSettingsStore();
const location = useLocation();
const isInAllView = isAllView(location.pathname, useParams());
@@ -41,7 +41,7 @@ const useCatalogFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolea
!deleted &&
!removed &&
state === 'succeeded' &&
(showTextOnlyThreads || (!showTextOnlyThreads && isMediaShowed)) &&
(!hideThreadsWithoutImages || (hideThreadsWithoutImages && isMediaShowed)) &&
cid &&
cid === postCid &&
subplebbitAddress === address &&
@@ -63,7 +63,7 @@ const useCatalogFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolea
}
// add subplebbit description and rules as fake posts at the top of the feed
if ((description && description.length > 0 && (showTextOnlyThreads || (!showTextOnlyThreads && suggested?.avatarUrl))) || (isInAllView && showTextOnlyThreads)) {
if (description && description.length > 0) {
_feed.unshift({
isDescription: true,
subplebbitAddress: address,
@@ -77,7 +77,8 @@ const useCatalogFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolea
});
}
if (rules && rules.length > 0 && showTextOnlyThreads) {
// rules are shown in description thread if both are set
if (rules && rules.length > 0 && !description) {
_feed.unshift({
isRules: true,
subplebbitAddress: address,
@@ -91,23 +92,7 @@ const useCatalogFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolea
}
return _feed;
}, [
accountComments,
feed,
description,
rules,
address,
isFeedLoaded,
createdAt,
title,
shortAddress,
avatarUrl,
t,
isInAllView,
multisub,
showTextOnlyThreads,
suggested?.avatarUrl,
]);
}, [accountComments, feed, description, rules, address, isFeedLoaded, createdAt, title, shortAddress, avatarUrl, t, isInAllView, multisub, hideThreadsWithoutImages]);
const rows = useMemo(() => {
const rows = [];
+5
View File
@@ -53,6 +53,11 @@ const useReplies = (comment: Comment) => {
});
}, [flattenedReplies, accountRepliesNotYetPublished]);
// if the comment is a fake comment for description or rules, return an empty array
if (comment?.isDescription || comment?.isRules) {
return [];
}
return repliesAndNotYetPublishedReplies;
};
+17
View File
@@ -37,3 +37,20 @@ export const formatMarkdown = (content: string): string => {
}
return md;
};
export function removeMarkdown(md: string): string {
return md
.replace(/\[([^\]]*?)\][\[\(].*?[\]\)]/g, '$1') // Remove links
.replace(/[*_~`]/g, '') // Remove emphasis and code markers
.replace(/^#+\s*(.+)$/gm, '$1') // Remove headers
.replace(/^\s*[-*+]\s+/gm, '') // Remove list markers
.replace(/^\s*>\s+/gm, '') // Remove blockquotes
.replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '') // Remove reference-style links
.replace(/^(\n)?\s{0,}#{1,6}\s*( (.+))? +#+$|^(\n)?\s{0,}#{1,6}\s*( (.+))?$/gm, '$1$3$4$6') // Remove atx-style headers
.replace(/([\*]+)(\S)(.*?\S)??\1/g, '$2$3') // Remove * emphasis
.replace(/(^|\W)([_]+)(\S)(.*?\S)??\2($|\W)/g, '$1$3$4$5') // Remove _ emphasis
.replace(/(`{3,})(.*?)\1/gm, '$2') // Remove code blocks
.replace(/`(.+?)`/g, '$1') // Remove inline code
.replace(/~(.*?)~/g, '$1') // Remove strike through
.trim();
}
+19 -27
View File
@@ -9,10 +9,6 @@ interface FilterItem {
}
interface CatalogFiltersStore {
showAdultBoards: boolean;
setShowAdultBoards: (value: boolean) => void;
showGoreBoards: boolean;
setShowGoreBoards: (value: boolean) => void;
showTextOnlyThreads: boolean;
setShowTextOnlyThreads: (value: boolean) => void;
filterText: string;
@@ -35,10 +31,6 @@ const useCatalogFiltersStore = create(
set({ showTextOnlyThreads: value, filteredCount: 0 });
get().updateFilter();
},
showAdultBoards: false,
setShowAdultBoards: (value: boolean) => set({ showAdultBoards: value }),
showGoreBoards: false,
setShowGoreBoards: (value: boolean) => set({ showGoreBoards: value }),
filterText: '',
setFilterText: (value: string) => set({ filterText: value }),
filterItems: [],
@@ -58,28 +50,28 @@ const useCatalogFiltersStore = create(
filter: (comment: Comment) => {
const { showTextOnlyThreads, filterItems } = state;
const shouldShow = (() => {
if (!showTextOnlyThreads && !getHasThumbnail(getCommentMediaInfo(comment), comment?.link)) {
return false;
const hasThumbnail = getHasThumbnail(getCommentMediaInfo(comment), comment?.link);
const title = comment?.title?.toLowerCase() || '';
const content = comment?.content?.toLowerCase() || '';
const matchesFilterItems = filterItems
.filter((item) => item.enabled)
.some((item) => {
const text = item.text.toLowerCase();
return title.includes(text) || content.includes(text);
});
const shouldShow = showTextOnlyThreads || hasThumbnail;
if (!shouldShow || matchesFilterItems) {
if (matchesFilterItems && !filteredCids.has(comment.cid)) {
filteredCids.add(comment.cid);
set((state) => ({ filteredCount: state.filteredCount + 1 }));
}
const title = comment?.title?.toLowerCase() || '';
const content = comment?.content?.toLowerCase() || '';
return !filterItems
.filter((item) => item.enabled)
.some((item) => {
const text = item.text.toLowerCase();
return title.includes(text) || content.includes(text);
});
})();
if (!shouldShow && !filteredCids.has(comment.cid)) {
filteredCids.add(comment.cid);
set((state) => ({ filteredCount: state.filteredCount + 1 }));
return false;
}
return shouldShow;
return true;
},
}));
},
@@ -0,0 +1,29 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface InterfaceSettingsStore {
hideGoreBoards: boolean;
setHideGoreBoards: (value: boolean) => void;
hideAdultBoards: boolean;
setHideAdultBoards: (value: boolean) => void;
hideThreadsWithoutImages: boolean;
setHideThreadsWithoutImages: (value: boolean) => void;
}
const useInterfaceSettingsStore = create(
persist<InterfaceSettingsStore>(
(set) => ({
hideGoreBoards: true,
setHideGoreBoards: (value: boolean) => set({ hideGoreBoards: value }),
hideAdultBoards: true,
setHideAdultBoards: (value: boolean) => set({ hideAdultBoards: value }),
hideThreadsWithoutImages: true,
setHideThreadsWithoutImages: (value: boolean) => set({ hideThreadsWithoutImages: value }),
}),
{
name: 'interface-settings-storage',
},
),
);
export default useInterfaceSettingsStore;
+16 -4
View File
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useRef } from 'react';
import { useLocation, useParams } from 'react-router-dom';
import { useAccount, useAccountComments, useBlock, useFeed, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import { Comment, useAccount, useAccountComments, useBlock, useFeed, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
import { useTranslation } from 'react-i18next';
import styles from './board.module.css';
@@ -17,13 +17,23 @@ import ReplyModal from '../../components/reply-modal';
import SettingsModal from '../../components/settings-modal';
import SubplebbitDescription from '../../components/subplebbit-description';
import SubplebbitRules from '../../components/subplebbit-rules';
import useInterfaceSettingsStore from '../../stores/use-interface-settings-store';
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
const threadsWithoutImagesFilter = (comment: Comment) => {
if (!getHasThumbnail(getCommentMediaInfo(comment), comment?.link)) {
return false;
}
return true;
};
const Board = () => {
const { t } = useTranslation();
const location = useLocation();
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
const { hideThreadsWithoutImages } = useInterfaceSettingsStore();
const isInAllView = isAllView(location.pathname, useParams());
const defaultSubplebbitAddresses = useDefaultSubplebbitAddresses();
@@ -51,8 +61,9 @@ const Board = () => {
sortType,
postsPerPage: isInAllView || isInSubscriptionsView ? 5 : 25,
...(isInAllView || isInSubscriptionsView ? { newerThan: timeFilterSeconds } : {}),
filter: hideThreadsWithoutImages ? threadsWithoutImagesFilter : undefined,
}),
[subplebbitAddresses, sortType, timeFilterSeconds, isInAllView, isInSubscriptionsView],
[subplebbitAddresses, sortType, timeFilterSeconds, isInAllView, isInSubscriptionsView, hideThreadsWithoutImages],
);
const { feed, hasMore, loadMore, reset } = useFeed(feedOptions);
@@ -75,12 +86,13 @@ const Board = () => {
!removed &&
state === 'succeeded' &&
cid &&
(hideThreadsWithoutImages ? getHasThumbnail(getCommentMediaInfo(comment), comment?.link) : true) &&
cid === postCid &&
comment?.subplebbitAddress === subplebbitAddress &&
!feed.some((post) => post.cid === cid)
);
}),
[accountComments, subplebbitAddress, feed],
[accountComments, subplebbitAddress, feed, hideThreadsWithoutImages],
);
// show newest account comment at the top of the feed but after pinned posts
@@ -180,7 +192,7 @@ const Board = () => {
)}
{feed.length !== 0 ? (
<>
{rules && rules.length > 0 && <SubplebbitRules subplebbitAddress={subplebbitAddress} createdAt={createdAt} rules={rules} />}
{rules && !description && rules.length > 0 && <SubplebbitRules subplebbitAddress={subplebbitAddress} createdAt={createdAt} rules={rules} />}
<Virtuoso
increaseViewportBy={{ bottom: 1200, top: 1200 }}
totalCount={combinedFeed.length}
@@ -1,25 +1,24 @@
import { useState, useCallback } from 'react';
import { useLocation, useParams } from 'react-router-dom';
import { useState, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { isAllView, isCatalogView } from '../../../lib/utils/view-utils';
import useCatalogFiltersStore from '../../../stores/use-catalog-filters-store';
import styles from './catalog-filters.module.css';
const FiltersTable = ({
localFilterItems,
setLocalFilterItems,
onSave,
}: {
localFilterItems: any[];
setLocalFilterItems: React.Dispatch<React.SetStateAction<any[]>>;
onSave: () => void;
}) => {
const FiltersTable = ({ onSave }: { onSave: () => void }) => {
const { t } = useTranslation();
const { saveAndApplyFilters } = useCatalogFiltersStore();
const { filterItems, saveAndApplyFilters } = useCatalogFiltersStore();
const [localFilterItems, setLocalFilterItems] = useState(filterItems);
const inputRefs = useRef<(HTMLInputElement | null)[]>([]);
const handleAddFilter = useCallback(() => {
setLocalFilterItems((prev) => [...prev, { text: '', enabled: true }]);
}, [setLocalFilterItems]);
setLocalFilterItems((prev) => {
const newIndex = prev.length;
setTimeout(() => {
inputRefs.current[newIndex]?.focus();
}, 0);
return [...prev, { text: '', enabled: true }];
});
}, []);
const handleSave = useCallback(() => {
const nonEmptyFilters = localFilterItems.filter((item) => item.text.trim() !== '');
@@ -27,31 +26,22 @@ const FiltersTable = ({
onSave();
}, [saveAndApplyFilters, localFilterItems, onSave]);
const updateLocalFilterItem = useCallback(
(index: number, item: any) => {
setLocalFilterItems((prev) => prev.map((f, i) => (i === index ? item : f)));
},
[setLocalFilterItems],
);
const updateLocalFilterItem = useCallback((index: number, item: any) => {
setLocalFilterItems((prev) => prev.map((f, i) => (i === index ? item : f)));
}, []);
const removeLocalFilterItem = useCallback(
(index: number) => {
setLocalFilterItems((prev) => prev.filter((_, i) => i !== index));
},
[setLocalFilterItems],
);
const removeLocalFilterItem = useCallback((index: number) => {
setLocalFilterItems((prev) => prev.filter((_, i) => i !== index));
}, []);
const moveLocalFilterItemUp = useCallback(
(index: number) => {
if (index === 0) return;
setLocalFilterItems((prev) => {
const newItems = [...prev];
[newItems[index - 1], newItems[index]] = [newItems[index], newItems[index - 1]];
return newItems;
});
},
[setLocalFilterItems],
);
const moveLocalFilterItemUp = useCallback((index: number) => {
if (index === 0) return;
setLocalFilterItems((prev) => {
const newItems = [...prev];
[newItems[index - 1], newItems[index]] = [newItems[index], newItems[index - 1]];
return newItems;
});
}, []);
return (
<table className={styles.filtersTable}>
@@ -87,6 +77,7 @@ const FiltersTable = ({
spellCheck='false'
value={item.text}
onChange={(e) => updateLocalFilterItem(index, { ...item, text: e.target.value })}
ref={(el) => (inputRefs.current[index] = el)}
/>
</td>
<td>
@@ -115,37 +106,6 @@ const FiltersTable = ({
const FiltersModal = ({ closeModal }: { closeModal: () => void }) => {
const { t } = useTranslation();
const { showAdultBoards, setShowAdultBoards, showGoreBoards, setShowGoreBoards, showTextOnlyThreads, setShowTextOnlyThreads, filterItems, saveAndApplyFilters } =
useCatalogFiltersStore();
const [localShowAdultBoards, setLocalShowAdultBoards] = useState(showAdultBoards);
const [localShowGoreBoards, setLocalShowGoreBoards] = useState(showGoreBoards);
const [localShowTextOnlyThreads, setLocalShowTextOnlyThreads] = useState(showTextOnlyThreads);
const [localFilterItems, setLocalFilterItems] = useState(filterItems);
const location = useLocation();
const params = useParams();
const isInCatalogView = isCatalogView(location.pathname, params);
const isInAllView = isAllView(location.pathname, params);
const handleSave = useCallback(() => {
setShowAdultBoards(localShowAdultBoards);
setShowGoreBoards(localShowGoreBoards);
setShowTextOnlyThreads(localShowTextOnlyThreads);
const nonEmptyFilters = localFilterItems.filter((item) => item.text.trim() !== '');
saveAndApplyFilters(nonEmptyFilters);
closeModal();
}, [
localShowAdultBoards,
localShowGoreBoards,
localShowTextOnlyThreads,
localFilterItems,
setShowAdultBoards,
setShowGoreBoards,
setShowTextOnlyThreads,
saveAndApplyFilters,
closeModal,
]);
return (
<>
@@ -155,33 +115,7 @@ const FiltersModal = ({ closeModal }: { closeModal: () => void }) => {
<span className={styles.title}>{t('filters')}</span>
<span className={styles.closeButton} title='close' onClick={closeModal} />
</div>
<div className={styles.filters}>
{isInCatalogView && (
<div>
<label className='capitalize'>
<input type='checkbox' checked={!localShowTextOnlyThreads} onChange={(e) => setLocalShowTextOnlyThreads(!e.target.checked)} />
{t('hide_threads_without_images')}
</label>
</div>
)}
{isInAllView && (
<div className={styles.nsfwLabels}>
<div>
<label>
<input type='checkbox' checked={!localShowGoreBoards} onChange={(e) => setLocalShowGoreBoards(!e.target.checked)} />
hide gore content
</label>
</div>
<div>
<label>
<input type='checkbox' checked={!localShowAdultBoards} onChange={(e) => setLocalShowAdultBoards(!e.target.checked)} />
hide adult content
</label>
</div>
</div>
)}
</div>
{isInCatalogView && <FiltersTable localFilterItems={localFilterItems} setLocalFilterItems={setLocalFilterItems} onSave={handleSave} />}
<FiltersTable onSave={closeModal} />
</div>
</>
);
+5 -3
View File
@@ -17,6 +17,7 @@ import CatalogRow from '../../components/catalog-row';
import LoadingEllipsis from '../../components/loading-ellipsis';
import SettingsModal from '../../components/settings-modal';
import styles from './catalog.module.css';
import useInterfaceSettingsStore from '../../stores/use-interface-settings-store';
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
@@ -27,7 +28,8 @@ const Catalog = () => {
const isInAllView = isAllView(location.pathname, useParams());
const defaultSubplebbits = useDefaultSubplebbits();
const { filter, showAdultBoards, showGoreBoards } = useCatalogFiltersStore();
const { hideAdultBoards, hideGoreBoards } = useInterfaceSettingsStore();
const { filter } = useCatalogFiltersStore();
const account = useAccount();
const subscriptions = account?.subscriptions;
@@ -40,7 +42,7 @@ const Catalog = () => {
const hasGoreTag = tags?.includes('gore');
const hasAdultTag = tags?.includes('adult');
if ((hasGoreTag && !showGoreBoards) || (hasAdultTag && !showAdultBoards)) {
if ((hasGoreTag && hideGoreBoards) || (hasAdultTag && hideAdultBoards)) {
return false;
}
return true;
@@ -54,7 +56,7 @@ const Catalog = () => {
return subscriptions || [];
}
return [subplebbitAddress];
}, [isInAllView, isInSubscriptionsView, subplebbitAddress, defaultSubplebbits, subscriptions, showAdultBoards, showGoreBoards]);
}, [isInAllView, isInSubscriptionsView, subplebbitAddress, defaultSubplebbits, subscriptions, hideAdultBoards, hideGoreBoards]);
const { imageSize } = useCatalogStyleStore();
const columnWidth = imageSize === 'Large' ? 270 : 180;
@@ -10,6 +10,7 @@ import { CatalogPostMedia } from '../../../components/catalog-row';
import LoadingEllipsis from '../../../components/loading-ellipsis';
import BoxModal from '../box-modal';
import { nsfwTags } from '../home';
import { removeMarkdown } from '../../../lib/utils/post-utils';
interface PopularThreadProps {
post: Comment;
@@ -17,11 +18,16 @@ interface PopularThreadProps {
boardShortAddress: string;
}
const PopularThreadCard = ({ post, boardTitle, boardShortAddress }: PopularThreadProps) => {
const { cid, subplebbitAddress, title } = post || {};
const commentMediaInfo = getCommentMediaInfo(post);
const ContentPreview = ({ content, maxLength = 99 }: { content: string; maxLength?: number }) => {
const plainText = removeMarkdown(content).trim().replaceAll('&nbsp;', '').replace(/\n\n/g, '\n').replaceAll('\n\n', '');
const truncatedText = plainText.length > maxLength ? `${plainText.substring(0, maxLength).trim()}...` : plainText;
let content = post?.content?.replace(/\n\n/g, '\n');
return truncatedText;
};
const PopularThreadCard = ({ post, boardTitle, boardShortAddress }: PopularThreadProps) => {
const { cid, content, subplebbitAddress, title } = post || {};
const commentMediaInfo = getCommentMediaInfo(post);
return (
<div className={styles.popularThread} key={cid}>
@@ -38,7 +44,7 @@ const PopularThreadCard = ({ post, boardTitle, boardShortAddress }: PopularThrea
{content && ': '}
</>
)}
{content && (content.length > 99 ? `${content.substring(0, 99).trim()}...` : `${content}`)}
{content && <ContentPreview content={content} maxLength={99} />}
</div>
</div>
);
+4
View File
@@ -163,6 +163,10 @@
overflow: hidden;
}
.rulesMessage {
font-weight: 700 !important;
}
.postDesktop .abbr {
color: var(--post-mobile-abbr-text-color);
font-size: var(--post-mobile-abbr-font-size);
+1748 -1715
View File
File diff suppressed because it is too large Load Diff