feat(home): add popular threads box

This commit is contained in:
plebeius.eth
2024-05-02 22:09:41 +02:00
parent 952d446c4d
commit 02b287fc12
4 changed files with 158 additions and 13 deletions
+2 -2
View File
@@ -7,7 +7,7 @@ import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies'; import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
import styles from './catalog-row.module.css'; import styles from './catalog-row.module.css';
const CatalogPostMedia = ({ commentMediaInfo, link }: { commentMediaInfo: any; link: string }) => { export const CatalogPostMedia = ({ commentMediaInfo }: { commentMediaInfo: any }) => {
const { patternThumbnailUrl, thumbnail, type, url } = commentMediaInfo || {}; const { patternThumbnailUrl, thumbnail, type, url } = commentMediaInfo || {};
const iframeThumbnail = patternThumbnailUrl || thumbnail; const iframeThumbnail = patternThumbnailUrl || thumbnail;
const gifFrameUrl = useFetchGifFirstFrame(type === 'gif' ? url : undefined); const gifFrameUrl = useFetchGifFirstFrame(type === 'gif' ? url : undefined);
@@ -91,7 +91,7 @@ const CatalogPost = ({ openMenu, post, toggleMenu }: CatalogPostProps) => {
<div className={styles.mediaPaddingWrapper}> <div className={styles.mediaPaddingWrapper}>
{threadIcons} {threadIcons}
<div className={styles.mediaWrapper} style={thumbnailDimensions}> <div className={styles.mediaWrapper} style={thumbnailDimensions}>
<CatalogPostMedia commentMediaInfo={commentMediaInfo} link={link} /> <CatalogPostMedia commentMediaInfo={commentMediaInfo} />
</div> </div>
</div> </div>
</Link> </Link>
+1 -1
View File
@@ -1 +1 @@
export { default } from './catalog-row'; export { default, CatalogPostMedia } from './catalog-row';
+62
View File
@@ -25,6 +25,11 @@
padding-bottom: 0.5em; padding-bottom: 0.5em;
} }
.boardsBox {
height: 343px;
overflow: hidden;
}
.boxBar, .infoboxBar { .boxBar, .infoboxBar {
padding-left: 0.5em; padding-left: 0.5em;
line-height: 2em; line-height: 2em;
@@ -167,6 +172,63 @@
text-decoration: underline; text-decoration: underline;
} }
.popularThreads {
font-size: 93%;
padding: 0.5em;
padding-top: 0.25em;
padding-bottom: 0;
line-height: 130%;
text-align: center;
}
.popularThreads .loading {
text-align: left !important;
}
.popularThread {
vertical-align: top;
display: inline-block;
word-wrap: break-word;
overflow: hidden;
margin-top: 5px;
padding: 5px 0 3px;
position: relative;
width: 175px;
max-height: 320px;
margin-bottom: 10px;
color: #800;
}
.popularThread .title {
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
display: block;
white-space: nowrap;
margin-bottom: 5px;
}
.popularThread .mediaContainer {
width: auto;
height: auto;
border: 1px solid #800;
display: inline-block;
overflow: hidden;
position: relative;
line-height: 0;
}
.popularThread .mediaContainer img {
max-width: 150px;
max-height: 150px;
}
.popularThread .threadContent {
margin-bottom: 5px;
padding: 0 2px;
white-space: pre-line;
}
@media (max-width: 640px) { @media (max-width: 640px) {
.content { .content {
min-width: 0; min-width: 0;
+93 -10
View File
@@ -1,10 +1,13 @@
import { useRef } from 'react'; import { useEffect, useRef, useState } from 'react';
import styles from './home.module.css'; import styles from './home.module.css';
import { Link, useNavigate } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { Subplebbit, useSubplebbits } from '@plebbit/plebbit-react-hooks'; import { Comment, Subplebbit, useSubplebbits } from '@plebbit/plebbit-react-hooks';
import packageJson from '../../../package.json'; import packageJson from '../../../package.json';
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits'; import useDefaultSubplebbits, { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
import { CatalogPostMedia } from '../../components/catalog-row';
import LoadingEllipsis from '../../components/loading-ellipsis';
const isValidAddress = (address: string): boolean => { const isValidAddress = (address: string): boolean => {
if (address.includes('/') || address.includes('\\') || address.includes(' ')) { if (address.includes('/') || address.includes('\\') || address.includes(' ')) {
@@ -66,11 +69,11 @@ const InfoBox = () => {
); );
}; };
const Boards = ({ subplebbits }: { subplebbits: (Subplebbit | undefined)[] }) => { const Boards = ({ defaultSubplebbits }: { defaultSubplebbits: any }) => {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div className={styles.box}> <div className={`${styles.box} ${styles.boardsBox}`}>
<div className={styles.boxBar}> <div className={styles.boxBar}>
<h2 className={styles.capitalize}>{t('boards')}</h2> <h2 className={styles.capitalize}>{t('boards')}</h2>
<span>{t('options')} </span> <span>{t('options')} </span>
@@ -79,7 +82,7 @@ const Boards = ({ subplebbits }: { subplebbits: (Subplebbit | undefined)[] }) =>
<div className={styles.column}> <div className={styles.column}>
<h3>Default SFW</h3> <h3>Default SFW</h3>
<div className={styles.list}> <div className={styles.list}>
{subplebbits {defaultSubplebbits
.filter((subplebbit: Subplebbit | undefined): subplebbit is Subplebbit => subplebbit !== undefined) .filter((subplebbit: Subplebbit | undefined): subplebbit is Subplebbit => subplebbit !== undefined)
.map((subplebbit: Subplebbit) => { .map((subplebbit: Subplebbit) => {
const address = subplebbit.address; const address = subplebbit.address;
@@ -105,15 +108,94 @@ const Boards = ({ subplebbits }: { subplebbits: (Subplebbit | undefined)[] }) =>
); );
}; };
const PopularThreads = () => { interface PopularThreadProps {
post: Comment;
boardTitle: string | undefined;
boardShortAddress: string;
}
const PopularThreadCard = ({ post, boardTitle, boardShortAddress }: PopularThreadProps) => {
const { cid, content, subplebbitAddress, title } = post || {};
const commentMediaInfo = getCommentMediaInfo(post);
return (
<div className={styles.popularThread} key={cid}>
<div className={styles.title}>{boardTitle || boardShortAddress}</div>
<div className={styles.mediaContainer}>
<Link to={`/p/${subplebbitAddress}/c/${cid}`}>
<CatalogPostMedia commentMediaInfo={commentMediaInfo} />
</Link>
</div>
<div className={styles.threadContent}>
{title && <b>{title}</b>}
{content && (content.length > 99 ? `: ${content.substring(0, 99)}...` : `: ${content}`)}
</div>
</div>
);
};
const PopularThreads = ({ subplebbits }: { subplebbits: any }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [popularPosts, setPopularPosts] = useState([]);
useEffect(() => {
let subplebbitToPost: any = {};
subplebbits.forEach((subplebbit: any) => {
let mostRecentPost = null;
if (subplebbit?.posts?.pages?.hot?.comments) {
for (const comment of Object.values(subplebbit.posts.pages.hot.comments) as Comment[]) {
const { deleted, locked, pinned, removed, replyCount, timestamp } = comment;
const commentMediaInfo = getCommentMediaInfo(comment);
const isMediaShowed = getHasThumbnail(commentMediaInfo, comment.link);
if (
isMediaShowed &&
replyCount >= 2 &&
!removed &&
!deleted &&
!locked &&
!pinned && // criteria
timestamp > Date.now() / 1000 - 60 * 60 * 24 * 30 // 30 days
) {
if (!mostRecentPost || comment.timestamp > mostRecentPost.timestamp) {
mostRecentPost = comment;
}
}
if (mostRecentPost) {
subplebbitToPost[subplebbit.address] = mostRecentPost;
}
}
}
});
const newPopularPosts: any = Object.values(subplebbitToPost)
.sort((a: any, b: any) => b.timestamp - a.timestamp)
.slice(0, 8);
setPopularPosts(newPopularPosts);
}, [subplebbits]);
return ( return (
<div className={styles.box}> <div className={styles.box}>
<div className={`${styles.boxBar} ${styles.color2ColorBar}`}> <div className={`${styles.boxBar} ${styles.color2ColorBar}`}>
<h2 className={styles.capitalize}>{t('popular_threads')}</h2> <h2 className={styles.capitalize}>{t('popular_threads')}</h2>
<span>{t('options')} </span> <span>{t('options')} </span>
</div> </div>
<div className={styles.boxContent}></div> <div className={`${styles.boxContent} ${popularPosts.length === 8 ? styles.popularThreads : ''}`}>
{popularPosts.length < 8 ? (
<span className={styles.loading}>
<LoadingEllipsis string={t('loading')} />
</span>
) : (
popularPosts.map((post: any) => (
<PopularThreadCard key={post.cid} post={post} boardTitle={post.subplebbitTitle || post.subplebbitAddress} boardShortAddress={post.subplebbitAddress} />
))
)}
</div>
</div> </div>
); );
}; };
@@ -219,6 +301,7 @@ const Footer = () => {
}; };
const Home = () => { const Home = () => {
const defaultSubplebbits = useDefaultSubplebbits();
const subplebbitAddresses = useDefaultSubplebbitAddresses(); const subplebbitAddresses = useDefaultSubplebbitAddresses();
const { subplebbits } = useSubplebbits({ subplebbitAddresses }); const { subplebbits } = useSubplebbits({ subplebbitAddresses });
@@ -231,8 +314,8 @@ const Home = () => {
</Link> </Link>
<SearchBar /> <SearchBar />
<InfoBox /> <InfoBox />
<Boards subplebbits={subplebbits} /> <Boards defaultSubplebbits={defaultSubplebbits} />
<PopularThreads /> <PopularThreads subplebbits={subplebbits} />
<Stats /> <Stats />
<Footer /> <Footer />
</div> </div>