Merge pull request #335 from plebbit/development

Development
This commit is contained in:
Tom (plebeius.eth)
2024-04-08 19:38:39 +02:00
committed by GitHub
55 changed files with 644 additions and 379 deletions
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "عرض الموضوع",
"return": "العودة",
"bottom": "القاع",
"top": "أعلى"
"top": "أعلى",
"thread_closed": "تم إغلاق الموضوع.",
"may_not_reply": "قد لا تتم الرد في هذا الوقت."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "থ্রেড দেখুন",
"return": "ফিরে যাও",
"bottom": "নীচে",
"top": "শীর্ষ"
"top": "শীর্ষ",
"thread_closed": "থ্রেড বন্ধ হয়েছে।",
"may_not_reply": "এই সময়ে আপনি প্রতিক্রিয়া দিতে পারবেন না।"
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Zobrazit Vlákno",
"return": "Návrat",
"bottom": "Dno",
"top": "Nahoře"
"top": "Nahoře",
"thread_closed": "Vlákno uzavřeno.",
"may_not_reply": "V tuto chvíli nemůžete odpovědět."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Vis Tråd",
"return": "Vende tilbage",
"bottom": "Bund",
"top": "Top"
"top": "Top",
"thread_closed": "Tråd lukket.",
"may_not_reply": "Du kan muligvis ikke svare på dette tidspunkt."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Thread Anzeigen",
"return": "Zurück",
"bottom": "Unten",
"top": "Oben"
"top": "Oben",
"thread_closed": "Thread geschlossen.",
"may_not_reply": "Sie können möglicherweise zu diesem Zeitpunkt nicht antworten."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Προβολή Νήματος",
"return": "Επιστροφή",
"bottom": "Κάτω",
"top": "Κορυφή"
"top": "Κορυφή",
"thread_closed": "Το νήμα έκλεισε.",
"may_not_reply": "Μπορεί να μην μπορείτε να απαντήσετε αυτή τη στιγμή."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "View Thread",
"return": "Return",
"bottom": "Bottom",
"top": "Top"
"top": "Top",
"thread_closed": "Thread closed.",
"may_not_reply": "You may not reply at this time."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Ver Hilo",
"return": "Regresar",
"bottom": "Fondo",
"top": "Arriba"
"top": "Arriba",
"thread_closed": "Hilo cerrado.",
"may_not_reply": "Es posible que no puedas responder en este momento."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "مشاهده نخ",
"return": "برگشت",
"bottom": "پایین",
"top": "بالا"
"top": "بالا",
"thread_closed": "موضوع بسته شده است.",
"may_not_reply": "شما ممکن است در این زمان پاسخ ندهید."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Näytä Ketju",
"return": "Palata",
"bottom": "Pohja",
"top": "Ylös"
"top": "Ylös",
"thread_closed": "Keskustelu suljettu.",
"may_not_reply": "Et ehkä voi vastata tällä hetkellä."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Tingnan ang Thread",
"return": "Bumalik",
"bottom": "Ilalim",
"top": "Itaas"
"top": "Itaas",
"thread_closed": "Thread na isara.",
"may_not_reply": "Hindi ka maaaring mag-reply sa oras na ito."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Voir le Fil",
"return": "Retour",
"bottom": "Bas",
"top": "Haut"
"top": "Haut",
"thread_closed": "Fil fermé.",
"may_not_reply": "Vous ne pouvez peut-être pas répondre en ce moment."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "הצג אשכול",
"return": "חזור",
"bottom": "תחתית",
"top": "עליון"
"top": "עליון",
"thread_closed": "השרשור נסגר.",
"may_not_reply": "אולי אינך יכול להגיב כרגע."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "थ्रेड देखें",
"return": "वापस",
"bottom": "नीचे",
"top": "ऊपर"
"top": "ऊपर",
"thread_closed": "थ्रेड बंद हो गया।",
"may_not_reply": "इस समय आप जवाब नहीं दे सकते।"
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Téma Megtekintése",
"return": "Visszatérés",
"bottom": "Alja",
"top": "Felső"
"top": "Felső",
"thread_closed": "A téma lezárult.",
"may_not_reply": "Jelenleg nem válaszolhatsz erre."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Lihat Thread",
"return": "Kembali",
"bottom": "Bawah",
"top": "Atas"
"top": "Atas",
"thread_closed": "Benang ditutup.",
"may_not_reply": "Anda mungkin tidak bisa membalas pada saat ini."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Visualizza Thread",
"return": "Indietro",
"bottom": "Vai sotto",
"top": "Vai sopra"
"top": "Vai sopra",
"thread_closed": "Thread chiuso.",
"may_not_reply": "Non puoi rispondere in questo momento."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "スレッドを見る",
"return": "戻る",
"bottom": "下へ",
"top": "上へ"
"top": "上へ",
"thread_closed": "スレッドが閉じられました。",
"may_not_reply": "現在、返信することはできません。"
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "스레드 보기",
"return": "돌아 가기",
"bottom": "하단",
"top": "맨 위로"
"top": "맨 위로",
"thread_closed": "스레드가 닫혔습니다.",
"may_not_reply": "현재 시간에는 답글을 달 수 없습니다."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "थ्रेड पहा",
"return": "परत जा",
"bottom": "खाली",
"top": "वर"
"top": "वर",
"thread_closed": "सूत्र बंद झाला.",
"may_not_reply": "हा वेळ उत्तर देण्यास शक्य नाही."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Bekijk Thread",
"return": "Terugkeer",
"bottom": "Onderkant",
"top": "Boven"
"top": "Boven",
"thread_closed": "Thread gesloten.",
"may_not_reply": "U kunt op dit moment mogelijk niet antwoorden."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Vis Tråd",
"return": "Retur",
"bottom": "Bunnen",
"top": "Topp"
"top": "Topp",
"thread_closed": "Tråd stengt.",
"may_not_reply": "Du kan kanskje ikke svare på dette tidspunktet."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Zobacz Wątek",
"return": "Powrót",
"bottom": "Dół",
"top": "Góra"
"top": "Góra",
"thread_closed": "Wątek zamknięty.",
"may_not_reply": "Może nie możesz teraz odpowiedzieć."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Ver Tópico",
"return": "Retorno",
"bottom": "Fundo",
"top": "Topo"
"top": "Topo",
"thread_closed": "Tópico fechado.",
"may_not_reply": "Você pode não conseguir responder neste momento."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Vizualizați Firul",
"return": "Întoarcere",
"bottom": "Partea de jos",
"top": "Sus"
"top": "Sus",
"thread_closed": "Thread închis.",
"may_not_reply": "Poate că nu puteți răspunde în acest moment."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Посмотреть Тему",
"return": "Возвращение",
"bottom": "Низ",
"top": "Верх"
"top": "Верх",
"thread_closed": "Тема закрыта.",
"may_not_reply": "Вы можете не ответить в это время."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Shiko Thread",
"return": "Kthimi",
"bottom": "Fund",
"top": "Sipër"
"top": "Sipër",
"thread_closed": "Fjala është e mbyllur.",
"may_not_reply": "Nuk mund të përgjigjeni në këtë kohë."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Visa Tråd",
"return": "Återgång",
"bottom": "Botten",
"top": "Topp"
"top": "Topp",
"thread_closed": "Tråd stängd.",
"may_not_reply": "Du kanske inte kan svara vid den här tiden."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "థ్రెడ్‌ను చూడండి",
"return": "రిటర్న్",
"bottom": "కిందకు",
"top": "మేలే"
"top": "మేలే",
"thread_closed": "థ్రెడ్ మూసివేయబడింది.",
"may_not_reply": "ఈ సమయంలో మీరు ప్రతిసారం ఇవ్వలేరు."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "ดูกระทู้",
"return": "กลับ",
"bottom": "ด้านล่าง",
"top": "ด้านบน"
"top": "ด้านบน",
"thread_closed": "เธรดถูกปิด",
"may_not_reply": "คุณอาจไม่สามารถตอบกลับได้ในขณะนี้"
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Konuyu Görüntüle",
"return": "Dönüş",
"bottom": "Alt",
"top": "Üst"
"top": "Üst",
"thread_closed": "Konu kapatıldı.",
"may_not_reply": "Şu anda cevap veremeyebilirsiniz."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Переглянути Тему",
"return": "Повернення",
"bottom": "Низ",
"top": "Верхній"
"top": "Верхній",
"thread_closed": "Тема закрита.",
"may_not_reply": "Можливо, ви не зможете відповісти зараз."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "تھریڈ دیکھیں",
"return": "واپس",
"bottom": "نیچے",
"top": "اوپر"
"top": "اوپر",
"thread_closed": "موضوع بند کر دیا گیا ہے۔",
"may_not_reply": "آپ اس وقت جواب نہیں دے سکتے۔"
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "Xem Chủ Đề",
"return": "Trở lại",
"bottom": "Đáy",
"top": "Đầu"
"top": "Đầu",
"thread_closed": "Chủ đề đã đóng.",
"may_not_reply": "Bạn có thể không thể trả lời vào thời điểm này."
}
+3 -1
View File
@@ -41,5 +41,7 @@
"view_thread": "查看线程",
"return": "返回",
"bottom": "底部",
"top": "顶部"
"top": "顶部",
"thread_closed": "主题已关闭。",
"may_not_reply": "您现在可能无法回复。"
}
+1 -1
View File
@@ -14,4 +14,4 @@
.app {
padding: 5px;
}
}
}
+18 -30
View File
@@ -1,7 +1,7 @@
import React, { useEffect } from 'react';
import { Outlet, Route, Routes, useLocation, useParams } from 'react-router-dom';
import { useEffect } from 'react';
import { Outlet, Route, Routes, useLocation } from 'react-router-dom';
import { isHomeView } from './lib/utils/view-utils';
import { Subplebbit as SubplebbitType, useSubplebbit, useSubplebbits } from '@plebbit/plebbit-react-hooks';
import { useSubplebbits } from '@plebbit/plebbit-react-hooks';
import { useDefaultSubplebbitAddresses } from './hooks/use-default-subplebbits';
import useTheme from './hooks/use-theme';
import styles from './app.module.css';
@@ -16,32 +16,6 @@ import { MobileBoardButtons } from './components/board-buttons';
import BoardStats from './components/board-stats';
import PostForm from './components/post-form';
interface BoardLayoutProps {
subplebbits: (SubplebbitType | undefined)[];
}
const BoardLayout = React.memo(({ subplebbits }: BoardLayoutProps) => {
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
const subplebbit = useSubplebbit({ subplebbitAddress });
const { address, createdAt, title } = subplebbit || {};
return (
<>
{address && (
<>
<BoardNav address={address} subplebbits={subplebbits} />
<BoardBanner title={title} address={address} />
<MobileBoardButtons address={address} />
<PostForm address={address} />
<BoardStats address={address} createdAt={createdAt} />
<DesktopBoardButtons address={address} />
</>
)}
<Outlet />
</>
);
});
const App = () => {
const [theme] = useTheme();
const location = useLocation();
@@ -62,13 +36,27 @@ const App = () => {
// </>
// );
const boardLayout = (
<>
<BoardNav subplebbits={subplebbits} />
<BoardBanner />
<MobileBoardButtons />
<PostForm />
<BoardStats />
<DesktopBoardButtons />
<Outlet />
</>
);
return (
<div className={`${styles.app} ${isInHomeView ? 'yotsuba' : theme}`}>
<Routes>
<Route path='/' element={<Home subplebbits={subplebbits} />} />
<Route element={<BoardLayout subplebbits={subplebbits} />}>
<Route element={boardLayout}>
<Route path='/p/:subplebbitAddress' element={<Subplebbit />} />
<Route path='/p/:subplebbitAddress/c/:commentCid' element={<PostPage />} />
<Route path='/p/:subplebbitAddress/description' element={<PostPage />} />
<Route path='/p/:subplebbitAddress/rules' element={<PostPage />} />
</Route>
<Route path='/settings' element={<Settings />} />
</Routes>
+7 -6
View File
@@ -1,3 +1,5 @@
import { useParams } from 'react-router-dom';
import { useSubplebbit } from '@plebbit/plebbit-react-hooks';
import { useState } from 'react';
import styles from './board-banner.module.css';
@@ -12,13 +14,12 @@ const ImageBanner = () => {
return <img src={imagePath} alt='banner' />;
};
interface BoardBannerProps {
title: string | undefined;
address: string | undefined;
}
const BoardBanner = ({ address, title }: BoardBannerProps) => {
const BoardBanner = () => {
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
const subplebbit = useSubplebbit({ subplebbitAddress });
const { address, title } = subplebbit || {};
const displayAddress = address && address.length > 10 && !address.includes('.') ? address.slice(0, 13).concat('...') : address;
return (
<div className={styles.content}>
<div className={styles.bannerCnt}>
@@ -5,7 +5,7 @@ import styles from './board-buttons.module.css';
import { isPostPageView } from '../../lib/utils/view-utils';
interface BoardButtonsProps {
address: string;
address: string | undefined;
}
const OptionsButton = () => {
@@ -51,7 +51,8 @@ const BottomButton = () => {
);
};
export const MobileBoardButtons = ({ address }: BoardButtonsProps) => {
export const MobileBoardButtons = () => {
const { subplebbitAddress: address } = useParams<{ subplebbitAddress: string }>();
const isInPostPage = isPostPageView(useLocation().pathname, useParams());
return (
<div className={styles.mobileBoardButtons}>
@@ -76,7 +77,8 @@ export const MobileBoardButtons = ({ address }: BoardButtonsProps) => {
);
};
export const DesktopBoardButtons = ({ address }: BoardButtonsProps) => {
export const DesktopBoardButtons = () => {
const { subplebbitAddress: address } = useParams<{ subplebbitAddress: string }>();
const isInPostPage = isPostPageView(useLocation().pathname, useParams());
return (
<div className={styles.desktopBoardButtons}>
@@ -33,11 +33,6 @@
text-decoration: var(--button-text-decoration-mobile);
}
.boardNavMobile .pageJump a:hover {
color: var(--button-text-color-hover-mobile);
text-decoration: var(--button-text-decoration-hover-mobile);
}
.boardNavMobile strong {
text-transform: capitalize;
padding-right: 5px;
+3 -3
View File
@@ -1,10 +1,9 @@
import { Link, useNavigate } from 'react-router-dom';
import { Link, useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Subplebbit } from '@plebbit/plebbit-react-hooks';
import styles from './board-nav.module.css';
interface BoardNavProps {
address?: string | undefined;
subplebbits: (Subplebbit | undefined)[];
currentSubplebbit?: string | undefined;
}
@@ -77,7 +76,8 @@ const BoardNavMobile = ({ subplebbits, currentSubplebbit }: BoardNavProps) => {
);
};
const BoardNav = ({ address, subplebbits }: BoardNavProps) => {
const BoardNav = ({ subplebbits }: BoardNavProps) => {
const { subplebbitAddress: address } = useParams();
return (
<>
<BoardNavDesktop subplebbits={subplebbits} />
@@ -1,3 +1,11 @@
.hide {
display: none;
}
.show {
display: block;
}
.content hr {
min-width: 468px;
margin-left: -4px;
@@ -12,7 +20,7 @@
}
.blotter tr {
font-size: 11px;
font-size: var(--stats-font-size);
}
.blotter tfoot {
+19 -8
View File
@@ -1,15 +1,26 @@
import { useState } from 'react';
import { useSubplebbitStats } from '@plebbit/plebbit-react-hooks';
import { useLocation, useParams } from 'react-router-dom';
import { useComment, useSubplebbit, useSubplebbitStats } from '@plebbit/plebbit-react-hooks';
import styles from './board-stats.module.css';
import { Trans, useTranslation } from 'react-i18next';
import { isDescriptionView, isRulesView } from '../../lib/utils/view-utils';
export interface BoardStatsProps {
address: string | undefined;
createdAt: number | undefined;
}
const BoardStats = ({ address, createdAt }: BoardStatsProps) => {
const BoardStats = () => {
const { t } = useTranslation();
const { commentCid, subplebbitAddress } = useParams<{ subplebbitAddress: string; commentCid: string }>();
const subplebbit = useSubplebbit({ subplebbitAddress });
const { address, createdAt } = subplebbit || {};
const location = useLocation();
const params = useParams();
const isInDescriptionView = isDescriptionView(location.pathname, params);
const isInRulesView = isRulesView(location.pathname, params);
const comment = useComment({ commentCid });
const { deleted, locked, removed } = comment || {};
const hideStats = deleted || locked || removed || isInDescriptionView || isInRulesView;
const stats = useSubplebbitStats({ subplebbitAddress: address });
const [showStats, setShowStats] = useState(true);
@@ -22,7 +33,7 @@ const BoardStats = ({ address, createdAt }: BoardStatsProps) => {
};
return (
<div className={styles.content}>
<div className={`${styles.content} ${hideStats ? styles.hide : styles.show}`}>
<table className={styles.blotter}>
<thead>
<tr>
+20 -9
View File
@@ -1,4 +1,4 @@
import React, { useState } from 'react';
import React from 'react';
import styles from './media.module.css';
import { CommentMediaInfo } from '../../lib/utils/media-utils';
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
@@ -34,7 +34,7 @@ const ThumbnailSmall = ({ style, children, thumbnailSmallPadding }: ThumbnailPro
</span>
);
const Thumbnail = ({ commentMediaInfo, isMobile, isReply, linkHeight, linkWidth }: MediaProps) => {
const Thumbnail = ({ commentMediaInfo, isMobile, isReply, linkHeight, linkWidth, setShowThumbnail }: MediaProps) => {
let displayWidth, displayHeight;
const maxThumbnailSize = isMobile || isReply ? 125 : 250;
if (linkWidth && linkHeight) {
@@ -46,19 +46,30 @@ const Thumbnail = ({ commentMediaInfo, isMobile, isReply, linkHeight, linkWidth
displayHeight = `${maxThumbnailSize}px`;
}
if (commentMediaInfo?.type === 'audio') {
displayWidth = '250px';
displayHeight = '75px';
}
let mediaComponent: React.ReactNode = null;
const iframeThumbnail = commentMediaInfo?.patternThumbnailUrl || commentMediaInfo?.thumbnail;
const gifFrameUrl = useFetchGifFirstFrame(commentMediaInfo?.type === 'gif' ? commentMediaInfo.url : undefined);
if (commentMediaInfo?.type === 'image') {
mediaComponent = <img src={commentMediaInfo.url} alt='' />;
mediaComponent = <img src={commentMediaInfo.url} alt='' onClick={() => setShowThumbnail(false)} />;
} else if (commentMediaInfo?.type === 'video') {
mediaComponent = commentMediaInfo.thumbnail ? <img src={commentMediaInfo.thumbnail} alt='' /> : <video src={`${commentMediaInfo.url}#t=0.001`} />;
mediaComponent = commentMediaInfo.thumbnail ? (
<img src={commentMediaInfo.thumbnail} alt='' />
) : (
<video src={`${commentMediaInfo.url}#t=0.001`} onClick={() => setShowThumbnail(false)} />
);
} else if (commentMediaInfo?.type === 'webpage') {
mediaComponent = <img src={commentMediaInfo.thumbnail} alt='' />;
mediaComponent = <img src={commentMediaInfo.thumbnail} alt='' onClick={() => setShowThumbnail(false)} />;
} else if (commentMediaInfo?.type === 'iframe') {
mediaComponent = iframeThumbnail ? <img src={iframeThumbnail} alt='' /> : null;
mediaComponent = iframeThumbnail ? <img src={iframeThumbnail} alt='' onClick={() => setShowThumbnail(false)} /> : null;
} else if (commentMediaInfo?.type === 'gif' && gifFrameUrl) {
mediaComponent = <img src={gifFrameUrl} alt='' />;
mediaComponent = <img src={gifFrameUrl} alt='' onClick={() => setShowThumbnail(false)} />;
} else if (commentMediaInfo?.type === 'audio') {
mediaComponent = <audio src={commentMediaInfo.url} controls />;
}
const thumbnailSmallPadding = isMobile ? styles.thumbnailMobile : styles.thumbnailReplyDesktop;
@@ -79,7 +90,7 @@ const Media = ({ commentMediaInfo, isMobile, isReply, linkHeight, linkWidth, sho
return (
<span className={styles.content}>
<span className={`${showThumbnail ? styles.show : styles.hide} ${styles.thumbnail}`} onClick={() => setShowThumbnail(false)}>
<span className={`${showThumbnail ? styles.show : styles.hide} ${styles.thumbnail}`}>
<Thumbnail
commentMediaInfo={commentMediaInfo}
isMobile={isMobile}
@@ -111,7 +122,7 @@ const Media = ({ commentMediaInfo, isMobile, isReply, linkHeight, linkWidth, sho
({commentMediaInfo?.type})
</div>
)}
{isMobile && (commentMediaInfo?.type === 'iframe' || commentMediaInfo?.type === 'video') && (
{isMobile && (commentMediaInfo?.type === 'iframe' || commentMediaInfo?.type === 'video' || commentMediaInfo?.type === 'audio') && (
<div className={styles.closeButton}>
<span className='button' onClick={() => setShowThumbnail(true)}>
{t('close')}
@@ -10,6 +10,15 @@
padding-bottom: 0.5em;
}
.closed {
font-size: x-large;
text-align: center;
color: red;
font-weight: 700;
padding-top: 100px;
padding-bottom: 100px;
}
@media (max-width: 640px) {
.postFormButtonDesktop {
display: none;
+43 -16
View File
@@ -1,31 +1,58 @@
import { useState } from 'react';
import { useTranslation } from 'react-i18next';
import styles from './post-form.module.css';
import { isPostPageView } from '../../lib/utils/view-utils';
import { useLocation, useParams } from 'react-router-dom';
import { useComment } from '@plebbit/plebbit-react-hooks';
import { isDescriptionView, isPostPageView, isRulesView } from '../../lib/utils/view-utils';
import styles from './post-form.module.css';
export interface PostFormProps {
address: string | undefined;
}
const PostForm = ({ address }: PostFormProps) => {
const PostForm = () => {
const { t } = useTranslation();
const location = useLocation();
const params = useParams();
const isInPostPage = isPostPageView(location.pathname, params);
const isInDescriptionView = isDescriptionView(location.pathname, params);
const isInRulesView = isRulesView(location.pathname, params);
const { subplebbitAddress, commentCid } = params || {};
const comment = useComment({ commentCid });
const { deleted, locked, removed } = comment || {};
const isThreadClosed = deleted || locked || removed || isInDescriptionView || isInRulesView;
const [showForm, setShowForm] = useState(false);
const isInPostPage = isPostPageView(useLocation().pathname, useParams());
return (
<>
<div className={styles.postFormButtonDesktop}>
[
<button className='button' onClick={() => setShowForm(true)}>
{isInPostPage ? t('post_a_reply') : t('start_new_thread')}
</button>
]
{isThreadClosed ? (
<div className={styles.closed}>
{t('thread_closed')}
<br />
{t('may_not_reply')}
</div>
) : (
<div>
[
<button className='button' onClick={() => setShowForm(true)}>
{isInPostPage ? t('post_a_reply') : t('start_new_thread')}
</button>
]
</div>
)}
</div>
<div className={styles.postFormButtonMobile}>
<button className='button' onClick={() => setShowForm(!showForm)}>
{showForm ? t('close_post_form') : isInPostPage ? t('post_a_reply') : t('start_new_thread')}
</button>
{isThreadClosed ? (
<div className={styles.closed}>
Thread closed.
<br />
You may not reply at this time.
</div>
) : (
<button className='button' onClick={() => setShowForm(true)}>
{isInPostPage ? t('post_a_reply') : t('start_new_thread')}
</button>
)}
</div>
</>
);
+6
View File
@@ -57,6 +57,7 @@
.postDesktop .checkbox, .replyDesktop .checkbox {
padding: 3px 7px 3px 4px;
filter: var(--filter80);
}
.postDesktop .subject {
@@ -368,6 +369,11 @@
text-align: right;
}
.capcodeMod {
color: var(--post-capcode-mod-text-color) !important;
text-transform: capitalize;
}
@media (max-width: 640px) {
.postDesktop {
display: none;
+243 -213
View File
@@ -1,7 +1,7 @@
import { useState } from 'react';
import { Link, useLocation, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Comment } from '@plebbit/plebbit-react-hooks';
import { Role, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
import { getCommentMediaInfoMemoized, getHasThumbnail } from '../../lib/utils/media-utils';
import { getFormattedDate } from '../../lib/utils/time-utils';
@@ -12,10 +12,158 @@ import styles from './post.module.css';
import Markdown from '../markdown';
import Media from '../media';
const ReplyDesktop = ({ reply }: Comment) => {
interface PostProps {
index?: number;
post?: any;
reply?: any;
roles?: Role[];
showAllReplies?: boolean;
}
const PostDesktop = ({ post, roles, showAllReplies }: PostProps) => {
const { t } = useTranslation();
const { author, cid, content, link, linkHeight, linkWidth, locked, pinned, postCid, replyCount, shortCid, subplebbitAddress, timestamp, title } = post || {};
const { address, displayName, shortAddress } = author || {};
const authorRole = roles?.[address]?.role;
const { isDescription, isRules } = post || {}; // custom properties, not from api
const isInPostPage = isPostPageView(useLocation().pathname, useParams());
const displayTitle = title && title.length > 75 ? title?.slice(0, 75) + '...' : title;
const displayContent = content && !isInPostPage && content.length > 1000 ? content?.slice(0, 1000) + '(...)' : content;
const commentMediaInfo = getCommentMediaInfoMemoized(post);
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
const [showThumbnail, setShowThumbnail] = useState(true);
const replies = useReplies(post);
const visibleLinkCount = useCountLinksInReplies(post, 5);
const totalLinkCount = useCountLinksInReplies(post);
const repliesCount = pinned ? replyCount : replyCount - 5;
const linkCount = pinned ? totalLinkCount : totalLinkCount - visibleLinkCount;
return (
<div className={styles.postDesktop}>
<div className={styles.hrWrapper}>
<hr />
</div>
{!isInPostPage && (
<span className={styles.hideButtonWrapper}>
<span className={`${styles.hideButton} ${styles.hideThread}`} />
</span>
)}
{commentMediaInfo?.url && (
<div className={styles.file}>
<div className={styles.fileText}>
{t('link')}:{' '}
<a href={commentMediaInfo?.url} target='_blank' rel='noopener noreferrer'>
{commentMediaInfo.url.length > 30 ? commentMediaInfo?.url?.slice(0, 30) + '...' : commentMediaInfo?.url}
</a>{' '}
({commentMediaInfo?.type})
{!showThumbnail && (commentMediaInfo?.type === 'iframe' || commentMediaInfo?.type === 'video' || commentMediaInfo?.type === 'audio') && (
<span>
{' '}
[
<span className={styles.closeMedia} onClick={() => setShowThumbnail(true)}>
{t('close')}
</span>
]
</span>
)}
</div>
{hasThumbnail && (
<Media
commentMediaInfo={commentMediaInfo}
isMobile={false}
isReply={false}
linkHeight={linkHeight}
linkWidth={linkWidth}
showThumbnail={showThumbnail}
setShowThumbnail={setShowThumbnail}
/>
)}
</div>
)}
<div className={styles.postInfo}>
<span className={styles.checkbox}>
<input type='checkbox' />
</span>
{title && <span className={styles.subject}>{displayTitle} </span>}
<span className={styles.nameBlock}>
<span className={`${styles.name} ${(isDescription || isRules || authorRole) && styles.capcodeMod}`}>
{displayName || 'Anonymous'}
{authorRole && ` ## Board ${authorRole}`}{' '}
</span>
{!(isDescription || isRules) && <span className={styles.userAddress}>(u/{shortAddress}) </span>}
</span>
<span className={styles.dateTime}>{getFormattedDate(timestamp)} </span>
<span className={styles.postNum}>
{!(isDescription || isRules) && (
<span className={styles.postNumLink}>
<Link to={`/p/${subplebbitAddress}/${cid}`} className={styles.linkToPost} title={t('link_to_post')}>
c/
</Link>
<span className={styles.replyToPost} title={t('reply_to_post')}>
{shortCid}
</span>
</span>
)}
{pinned && (
<span className={styles.stickyIconWrapper}>
<img src='assets/icons/sticky.gif' alt='' className={styles.stickyIcon} title={t('sticky')} />
</span>
)}
{locked && (
<span className={styles.closedIconWrapper}>
<img src='assets/icons/closed.gif' alt='' className={styles.closedIcon} title={t('closed')} />
</span>
)}
<span className={styles.replyButton}>
[<Link to={`/p/${subplebbitAddress}/${isDescription ? 'description' : isRules ? 'rules' : `c/${postCid}`}`}>{t('reply')}</Link>]
</span>
</span>
<span className={styles.postMenuBtn}></span>
</div>
{!content && <div className={styles.spacer} />}
{content && (
<blockquote className={styles.postMessage}>
<Markdown content={displayContent} />
{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.
</span>
)}
</blockquote>
)}
{(replies.length > 5 || (pinned && replies.length > 0)) && !isInPostPage && (
<span className={styles.summary}>
<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.
</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>
))}
</div>
);
};
const ReplyDesktop = ({ reply, roles }: PostProps) => {
const { t } = useTranslation();
const { author, content, link, linkHeight, linkWidth, parentCid, pinned, postCid, shortCid, subplebbitAddress, timestamp } = reply || {};
const { displayName, shortAddress } = author || {};
const { address, displayName, shortAddress } = author || {};
const authorRole = roles?.[address]?.role;
const commentMediaInfo = getCommentMediaInfoMemoized(reply);
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
@@ -32,7 +180,10 @@ const ReplyDesktop = ({ reply }: Comment) => {
<input type='checkbox' />
</span>
<span className={styles.nameBlock}>
<span className={styles.name}>{displayName || 'Anonymous'} </span>
<span className={`${styles.name} ${authorRole && styles.capcodeMod}`}>
{displayName || 'Anonymous'}
{authorRole && ` ## Board ${authorRole}`}{' '}
</span>
<span className={styles.userAddress}>(u/{shortAddress}) </span>
</span>
<span className={styles.dateTime}>{getFormattedDate(timestamp)} </span>
@@ -60,7 +211,7 @@ const ReplyDesktop = ({ reply }: Comment) => {
<a href={link} target='_blank' rel='noopener noreferrer'>
{link.length > 30 ? link?.slice(0, 30) + '...' : link}
</a>
{!showThumbnail && (commentMediaInfo?.type === 'iframe' || commentMediaInfo?.type === 'video') && (
{!showThumbnail && (commentMediaInfo?.type === 'iframe' || commentMediaInfo?.type === 'video' || commentMediaInfo?.type === 'audio') && (
<span>
{' '}
[
@@ -102,140 +253,111 @@ const ReplyDesktop = ({ reply }: Comment) => {
);
};
const PostDesktop = ({ post, showAllReplies }: { post: Comment; showAllReplies: boolean }) => {
const PostMobile = ({ post, roles, showAllReplies }: PostProps) => {
const { t } = useTranslation();
const { author, cid, content, link, linkHeight, linkWidth, locked, pinned, postCid, replyCount, shortCid, subplebbitAddress, timestamp, title } = post || {};
const { displayName, shortAddress } = author || {};
const { author, cid, content, link, linkHeight, linkWidth, pinned, replyCount, shortCid, subplebbitAddress, timestamp, title } = post || {};
const { address, displayName, shortAddress } = author || {};
const authorRole = roles?.[address]?.role;
const { isDescription, isRules } = post || {}; // custom properties, not from api
const linkCount = useCountLinksInReplies(post);
const isInPostPage = isPostPageView(useLocation().pathname, useParams());
const displayTitle = title && title.length > 75 ? title?.slice(0, 75) + '...' : title;
const displayContent = content && !isInPostPage && content.length > 1000 ? content?.slice(0, 1000) + '(...)' : content;
const displayTitle = title && title.length > 30 ? title?.slice(0, 30) + '(...)' : title;
const displayContent = content && !isInPostPage && content.length > 1000 ? content?.slice(0, 1000) : content;
const commentMediaInfo = getCommentMediaInfoMemoized(post);
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
const [showThumbnail, setShowThumbnail] = useState(true);
const replies = useReplies(post);
const visibleLinkCount = useCountLinksInReplies(post, 5);
const totalLinkCount = useCountLinksInReplies(post);
const repliesCount = pinned ? replyCount : replyCount - 5;
const linkCount = pinned ? totalLinkCount : totalLinkCount - visibleLinkCount;
return (
<div className={styles.postDesktop}>
<div className={styles.postMobile}>
<div className={styles.hrWrapper}>
<hr />
</div>
{!isInPostPage && (
<span className={styles.hideButtonWrapper}>
<span className={`${styles.hideButton} ${styles.hideThread}`} />
</span>
)}
{commentMediaInfo?.url && (
<div className={styles.file}>
<div className={styles.fileText}>
{t('link')}:{' '}
<a href={commentMediaInfo?.url} target='_blank' rel='noopener noreferrer'>
{commentMediaInfo.url.length > 30 ? commentMediaInfo?.url?.slice(0, 30) + '...' : commentMediaInfo?.url}
</a>{' '}
({commentMediaInfo?.type})
{!showThumbnail && (commentMediaInfo?.type === 'iframe' || commentMediaInfo?.type === 'video') && (
<span>
{' '}
[
<span className={styles.closeMedia} onClick={() => setShowThumbnail(true)}>
{t('close')}
</span>
]
<div className={styles.thread}>
<div className={styles.postContainer}>
<div className={styles.postOp}>
<div className={styles.postInfo}>
<span className={styles.postMenuBtn} title='Post menu'>
...
</span>
<span className={styles.nameBlock}>
<span className={`${styles.name} ${authorRole && styles.capcodeMod}`}>
{displayName || 'Anonymous'}
{authorRole && ` ## Board ${authorRole}`}{' '}
</span>
{!(isDescription || isRules) && <span className={styles.address}>(u/{shortAddress || address?.slice(0, 12) + '...'})</span>}
{title && (
<>
<br />
<span className={styles.subject}>{displayTitle}</span>
</>
)}
</span>
<span className={styles.dateTimePostNum}>
{getFormattedDate(timestamp)}{' '}
{!(isDescription || isRules) && (
<>
<span className={styles.linkToPost}>c/</span>
<span className={styles.replyToPost}>{shortCid}</span>
</>
)}
</span>
</div>
{hasThumbnail && (
<Media
commentMediaInfo={commentMediaInfo}
isMobile={true}
isReply={false}
linkHeight={linkHeight}
linkWidth={linkWidth}
showThumbnail={showThumbnail}
setShowThumbnail={setShowThumbnail}
/>
)}
{content && (
<blockquote className={`${styles.postMessage} ${styles.clampLines}`}>
<Markdown content={displayContent} />
{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.
</span>
)}
</blockquote>
)}
</div>
{hasThumbnail && (
<Media
commentMediaInfo={commentMediaInfo}
isMobile={false}
isReply={false}
linkHeight={linkHeight}
linkWidth={linkWidth}
showThumbnail={showThumbnail}
setShowThumbnail={setShowThumbnail}
/>
{!isInPostPage && (
<div className={styles.postLink}>
<span className={styles.info}>
{replyCount > 0 && `${replyCount} Replies`}
{linkCount > 0 && ` / ${linkCount} Links`}
</span>
<Link to={`/p/${subplebbitAddress}/${isDescription ? 'description' : isRules ? 'rules' : `c/${cid}`}`} className='button'>
{t('view_thread')}
</Link>
</div>
)}
</div>
)}
<div className={styles.postInfo}>
<span className={styles.checkbox}>
<input type='checkbox' />
</span>
{title && <span className={styles.subject}>{displayTitle} </span>}
<span className={styles.nameBlock}>
<span className={styles.name}>{displayName || 'Anonymous'} </span>
<span className={styles.userAddress}>(u/{shortAddress}) </span>
</span>
<span className={styles.dateTime}>{getFormattedDate(timestamp)} </span>
<span className={styles.postNum}>
<span className={styles.postNumLink}>
<Link to={`/p/${subplebbitAddress}/c/${cid}`} className={styles.linkToPost} title={t('link_to_post')}>
c/
</Link>
<span className={styles.replyToPost} title={t('reply_to_post')}>
{shortCid}
</span>
</span>
{pinned && (
<span className={styles.stickyIconWrapper}>
<img src='assets/icons/sticky.gif' alt='' className={styles.stickyIcon} title={t('sticky')} />
</span>
)}
{locked && (
<span className={styles.closedIconWrapper}>
<img src='assets/icons/closed.gif' alt='' className={styles.closedIcon} title={t('closed')} />
</span>
)}
<span className={styles.replyButton}>
[<Link to={`/p/${subplebbitAddress}/c/${postCid}`}>{t('reply')}</Link>]
</span>
</span>
<span className={styles.postMenuBtn}></span>
{!(pinned && !isInPostPage) &&
replies &&
(showAllReplies ? replies : replies.slice(-5)).map((reply, index) => (
<div key={reply.cid} className={styles.replyContainer}>
<ReplyMobile index={index} reply={reply} roles={roles} />
</div>
))}
</div>
{!content && <div className={styles.spacer} />}
{content && (
<blockquote className={styles.postMessage}>
<Markdown content={displayContent} />
{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.
</span>
)}
</blockquote>
)}
{(replies.length > 5 || pinned) && !isInPostPage && (
<span className={styles.summary}>
<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.
</span>
)}
{!(pinned && !isInPostPage) &&
replies &&
replies.slice(0, showAllReplies ? replies.length : 5).map((reply, index) => (
<div key={reply.cid} className={styles.replyContainer}>
<ReplyDesktop index={index} reply={reply} />
</div>
))}
</div>
);
};
const ReplyMobile = ({ reply }: Comment) => {
const ReplyMobile = ({ reply, roles }: PostProps) => {
const { author, content, link, linkHeight, linkWidth, parentCid, pinned, postCid, shortCid, subplebbitAddress, timestamp } = reply || {};
const { displayName, shortAddress } = author || {};
const { address, displayName, shortAddress } = author || {};
const authorRole = roles?.[address]?.role;
const commentMediaInfo = getCommentMediaInfoMemoized(reply);
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
@@ -250,7 +372,10 @@ const ReplyMobile = ({ reply }: Comment) => {
<div className={styles.postInfo}>
<span className={styles.postMenuBtn}>...</span>
<span className={styles.nameBlock}>
<span className={styles.name}>{displayName || 'Anonymous'} </span>
<span className={`${styles.name} ${authorRole && styles.capcodeMod}`}>
{displayName || 'Anonymous'}
{authorRole && ` ## Board ${authorRole}`}{' '}
</span>
<span className={styles.address}>(u/{shortAddress})</span>
</span>
<span className={styles.dateTimePostNum}>
@@ -288,108 +413,13 @@ const ReplyMobile = ({ reply }: Comment) => {
);
};
const PostMobile = ({ post, showAllReplies }: { post: Comment; showAllReplies: boolean }) => {
const { t } = useTranslation();
const { author, cid, content, link, linkHeight, linkWidth, pinned, replyCount, shortCid, subplebbitAddress, timestamp, title } = post || {};
const { address, displayName, shortAddress } = author || {};
const linkCount = useCountLinksInReplies(post);
const isInPostPage = isPostPageView(useLocation().pathname, useParams());
const displayTitle = title && title.length > 30 ? title?.slice(0, 30) + '(...)' : title;
const displayContent = content && !isInPostPage && content.length > 1000 ? content?.slice(0, 1000) : content;
const commentMediaInfo = getCommentMediaInfoMemoized(post);
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
const [showThumbnail, setShowThumbnail] = useState(true);
const replies = useReplies(post);
return (
<div className={styles.postMobile}>
<div className={styles.hrWrapper}>
<hr />
</div>
<div className={styles.thread}>
<div className={styles.postContainer}>
<div className={styles.postOp}>
<div className={styles.postInfo}>
<span className={styles.postMenuBtn} title='Post menu'>
...
</span>
<span className={styles.nameBlock}>
<span className={styles.name}>{displayName || 'Anonymous'} </span>
<span className={styles.address}>(u/{shortAddress || address?.slice(0, 12) + '...'})</span>
{title && (
<>
<br />
<span className={styles.subject}>{displayTitle}</span>
</>
)}
</span>
<span className={styles.dateTimePostNum}>
{getFormattedDate(timestamp)} <span className={styles.linkToPost}>c/</span>
<span className={styles.replyToPost}>{shortCid}</span>
</span>
</div>
{hasThumbnail && (
<Media
commentMediaInfo={commentMediaInfo}
isMobile={true}
isReply={false}
linkHeight={linkHeight}
linkWidth={linkWidth}
showThumbnail={showThumbnail}
setShowThumbnail={setShowThumbnail}
/>
)}
{content && (
<blockquote className={`${styles.postMessage} ${styles.clampLines}`}>
<Markdown content={displayContent} />
{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.
</span>
)}
</blockquote>
)}
</div>
{!isInPostPage && (
<div className={styles.postLink}>
<span className={styles.info}>
{replyCount} Replies
{linkCount > 0 && ` / ${linkCount} Links`}
</span>
<Link to={`/p/${subplebbitAddress}/c/${cid}`} className='button'>
{t('view_thread')}
</Link>
</div>
)}
</div>
{!(pinned && !isInPostPage) &&
replies &&
replies.slice(0, showAllReplies ? replies.length : 5).map((reply, index) => (
<div key={reply.cid} className={styles.replyContainer}>
<ReplyMobile index={index} reply={reply} />
</div>
))}
</div>
</div>
);
};
interface PostProps {
index?: number;
post: Comment;
showAllReplies?: boolean;
}
const Post = ({ post, showAllReplies = false }: PostProps) => {
const subplebbit = useSubplebbit({ subplebbitAddress: post?.subplebbitAddress });
return (
<div className={styles.thread}>
<div className={styles.postContainer}>
<PostDesktop post={post} showAllReplies={showAllReplies} />
<PostMobile post={post} showAllReplies={showAllReplies} />
<PostDesktop post={post} roles={subplebbit?.roles} showAllReplies={showAllReplies} />
<PostMobile post={post} roles={subplebbit?.roles} showAllReplies={showAllReplies} />
</div>
</div>
);
-5
View File
@@ -38,11 +38,6 @@ hr {
user-select: none;
display: inline-block;
}
.button:hover {
color: var(--button-text-color-hover-mobile);
cursor: pointer;
}
}
@media (min-width: 640px) {
+1
View File
@@ -17,6 +17,7 @@ export const getHasThumbnail = (commentMediaInfo: CommentMediaInfo | undefined,
commentMediaInfo &&
(commentMediaInfo.type === 'image' ||
commentMediaInfo.type === 'video' ||
commentMediaInfo.type === 'audio' ||
commentMediaInfo.type === 'gif' ||
(commentMediaInfo.type === 'webpage' && commentMediaInfo.thumbnail) ||
(commentMediaInfo.type === 'iframe' && iframeThumbnail))
+13 -1
View File
@@ -7,8 +7,20 @@ export const isHomeView = (pathname: string): boolean => {
return pathname === '/';
};
export const isDescriptionView = (pathname: string, params: ParamsType): boolean => {
const decodedPathname = decodeURIComponent(pathname);
return params.subplebbitAddress ? decodedPathname.startsWith(`/p/${params.subplebbitAddress}/description`) : false;
};
export const isRulesView = (pathname: string, params: ParamsType): boolean => {
const decodedPathname = decodeURIComponent(pathname);
return params.subplebbitAddress ? decodedPathname.startsWith(`/p/${params.subplebbitAddress}/rules`) : false;
};
export const isPostPageView = (pathname: string, params: ParamsType): boolean => {
// some subs might use emojis in their address, so we need to decode the pathname
if (isDescriptionView(pathname, params) || isRulesView(pathname, params)) {
return true;
}
const decodedPathname = decodeURIComponent(pathname);
return params.subplebbitAddress && params.commentCid ? decodedPathname.startsWith(`/p/${params.subplebbitAddress}/c/${params.commentCid}`) : false;
};
+61 -27
View File
@@ -100,6 +100,13 @@
--post-mobile-file-info-text-color: #707070;
--post-mobile-content-font-size: 11pt;
/* post capcodes */
--post-capcode-mod-text-color: purple;
/* post form */
--post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700;
/* quotelink */
--post-quotelink-text-color: navy;
--post-quotelink-text-color-hover: red;
@@ -111,9 +118,8 @@
--reply-desktop-background-color: #f0e0d6;
--reply-desktop-border: 1px solid #d9bfb7;
/* post form */
--post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700;
/* stats */
--stats-font-size: 11px;
}
:root .yotsuba-b {
@@ -218,6 +224,13 @@
--post-mobile-abbr-font-size: 10pt;
--post-mobile-content-font-size: 11pt;
/* post capcodes */
--post-capcode-mod-text-color: purple;
/* post form */
--post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700;
/* quotelink */
--post-quotelink-text-color: #d00;
--post-quotelink-text-color-hover: #d00;
@@ -229,9 +242,8 @@
--reply-desktop-background-color: #d6daf0;
--reply-desktop-border: 1px solid #b7c5d9;
/* post form */
--post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700;
/* stats */
--stats-font-size: 11px;
}
:root .futaba {
@@ -264,15 +276,12 @@
--button-text-decoration: underline;
/* mobile buttons */
--button-font-size-mobile: 12pt;
--button-font-weight-mobile: unset;
--button-text-color-mobile: #00e;
--button-background-image-mobile: none;
--button-background-color-mobile: none;
--button-border-mobile: none;
--button-text-decoration-mobile: underline;
--button-text-color-hover-mobile: red;
--button-text-decoration-hover-mobile: underline;
--button-font-size-mobile: 11pt;
--button-font-weight-mobile: 700;
--button-text-color-mobile: #800;
--button-background-image-mobile: url("/public/assets/buttons/button-fade.png");
--button-background-color-mobile: #f0e0d6;
--button-border-mobile: 1px solid #c0a69d;
/* footer */
--footer-item-background-color-desktop: #fed;
@@ -314,6 +323,13 @@
--post-mobile-abbr-font-size: 12pt;
--post-mobile-content-font-size: 13pt;
/* post capcodes */
--post-capcode-mod-text-color: purple;
/* post form */
--post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700;
/* quotelink */
--post-quotelink-text-color: navy;
--post-quotelink-text-color-hover: red;
@@ -325,9 +341,8 @@
--reply-desktop-background-color: #f0e0d6;
--reply-desktop-border: 1px solid #d9bfb7;
/* post form */
--post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700;
/* stats */
--stats-font-size: 10pt;
}
:root .burichan {
@@ -380,8 +395,6 @@
--button-background-image-mobile: url("/public/assets/buttons/button-fade-blue.png");
--button-background-color-mobile: #d6daf0;
--button-border-mobile: 1px solid #b7c5d9;
--button-text-color-hover-mobile: red;
--button-text-decoration-hover-mobile: underline;
/* footer */
--footer-item-background-color-desktop: #eef2ff;
@@ -423,6 +436,13 @@
--post-mobile-abbr-font-size: 12pt;
--post-mobile-content-font-size: 13pt;
/* post capcodes */
--post-capcode-mod-text-color: purple;
/* post form */
--post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700;
/* quotelink */
--post-quotelink-text-color: #d00;
--post-quotelink-text-color-hover: #d00;
@@ -433,10 +453,9 @@
--side-arrows-color: #b7c5d9;
--reply-desktop-background-color: #d6daf0;
--reply-desktop-border: 1px solid #b7c5d9;
/* post form */
--post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700;
/* stats */
--stats-font-size: 10pt;
}
:root .tomorrow {
@@ -478,6 +497,9 @@
--button-desktop-text-color: #81a2be;
--button-desktop-text-color-hover: #5f89ac;
/* filter for inputs */
--filter80: brightness(85%);
/* mobile buttons */
--button-font-size-mobile: 10pt;
--button-font-weight-mobile: 700;
@@ -529,6 +551,9 @@
--post-mobile-abbr-font-size: 10pt;
--post-mobile-content-font-size: 11pt;
/* post capcodes */
--post-capcode-mod-text-color: #81a2be;
/* quotelink */
--post-quotelink-text-color: #5f89ac;
--post-quotelink-text-color-hover: #81a2be;
@@ -548,6 +573,9 @@
--select-border: 1px solid #575a60;
--select-background-color: #282a2e;
--select-color: #c5c8c6;
/* stats */
--stats-font-size: 11px;
}
:root .photon {
@@ -632,6 +660,13 @@
--post-mobile-content-font-size: 11pt;
--post-mobile-border-bottom: 1px solid #ccc;
/* post capcodes */
--post-capcode-mod-text-color: #f60;
/* post form */
--post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700;
/* quotelink */
--post-quotelink-text-color: #f60;
--post-quotelink-text-color-hover: #f30;
@@ -643,7 +678,6 @@
--reply-desktop-background-color: #ddd;
--reply-desktop-border: 1px solid #ccc;
/* post form */
--post-form-toggle-font-size: 22px;
--post-form-toggle-font-weight: 700;
/* stats */
--stats-font-size: 11px;
}
+19 -4
View File
@@ -1,12 +1,21 @@
import { useEffect } from 'react';
import { useComment } from '@plebbit/plebbit-react-hooks';
import { useParams } from 'react-router-dom';
import { useComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
import { useLocation, useParams } from 'react-router-dom';
import Post from '../../components/post/post';
import styles from './post-page.module.css';
import { DescriptionPost, RulesPost } from '../subplebbit/subplebbit';
import { isDescriptionView, isRulesView } from '../../lib/utils/view-utils';
const PostPage = () => {
const params = useParams();
const { commentCid } = params;
const { commentCid, subplebbitAddress } = params;
const subplebbit = useSubplebbit({ subplebbitAddress });
const { createdAt, description, rules, suggested, title } = subplebbit;
const location = useLocation();
const isInDescriptionView = isDescriptionView(location.pathname, params);
const isInRulesView = isRulesView(location.pathname, params);
const post = useComment({ commentCid });
@@ -16,7 +25,13 @@ const PostPage = () => {
return (
<div className={styles.content}>
<Post post={post} showAllReplies={true} />
{isInDescriptionView ? (
<DescriptionPost avatarUrl={suggested?.avatarUrl} createdAt={createdAt} description={description} subplebbitAddress={subplebbitAddress} title={title} />
) : isInRulesView ? (
<RulesPost createdAt={createdAt} rules={rules} subplebbitAddress={subplebbitAddress} />
) : (
<Post post={post} showAllReplies={true} />
)}
</div>
);
};
+7 -11
View File
@@ -1,13 +1,9 @@
.debug {
position: fixed;
top: 50px;
right: 0;
padding: 0.5em;
.footer {
padding-top: 10px;
}
.debug select {
margin: 2px;
border: var(--select-border);
background-color: var(--select-background-color);
color: var(--select-color);
}
@media (max-width: 768px) {
.footer {
padding-left: 5px;
}
}
+55 -1
View File
@@ -10,6 +10,52 @@ import Post from '../../components/post';
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
interface DescriptionPostProps {
subplebbitAddress: string | undefined;
createdAt: number;
description: string;
avatarUrl?: string;
title: string;
}
export const DescriptionPost = ({ subplebbitAddress, createdAt, description, avatarUrl, title }: DescriptionPostProps) => {
const post = {
isDescription: true,
subplebbitAddress,
timestamp: createdAt,
author: { displayName: '## Board Mods' },
content: description,
link: avatarUrl,
title: 'Welcome to ' + title,
pinned: true,
locked: true,
};
return <Post post={post} />;
};
interface RulesPostProps {
subplebbitAddress: string | undefined;
createdAt: number;
rules: string[];
}
export const RulesPost = ({ subplebbitAddress, createdAt, rules }: RulesPostProps) => {
const content = rules.map((rule, index) => `${index + 1}. ${rule}`).join('\n');
const post = {
isRules: true,
subplebbitAddress,
timestamp: createdAt,
author: { displayName: '## Board Mods' },
content,
title: 'Rules',
pinned: true,
locked: true,
};
return <Post post={post} />;
};
const Subplebbit = () => {
const { t } = useTranslation();
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
@@ -18,7 +64,7 @@ const Subplebbit = () => {
const { feed, hasMore, loadMore } = useFeed({ subplebbitAddresses, sortType });
const subplebbit = useSubplebbit({ subplebbitAddress });
const { shortAddress, state, title } = subplebbit || {};
const { createdAt, description, rules, shortAddress, state, suggested, title } = subplebbit || {};
const loadingStateString = useFeedStateString(subplebbitAddresses) || t('loading');
const loadingString = <div className={styles.stateString}>{state === 'failed' ? state : <LoadingEllipsis string={loadingStateString} />}</div>;
@@ -56,6 +102,14 @@ const Subplebbit = () => {
return (
<div className={styles.content}>
{createdAt && (
<>
{rules && rules.length > 0 && <RulesPost subplebbitAddress={subplebbitAddress} createdAt={createdAt} rules={rules} />}
{description && description.length > 0 && (
<DescriptionPost avatarUrl={suggested?.avatarUrl} subplebbitAddress={subplebbitAddress} createdAt={createdAt} description={description} title={title} />
)}
</>
)}
<Virtuoso
increaseViewportBy={{ bottom: 1200, top: 1200 }}
totalCount={feed?.length || 0}