Merge pull request #342 from plebbit/development

Development
This commit is contained in:
Tom (plebeius.eth)
2024-04-18 17:46:48 +02:00
committed by GitHub
14 changed files with 208 additions and 114 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 403 KiB

+2 -7
View File
@@ -1,8 +1,6 @@
import { useEffect } from 'react';
import { Outlet, Route, Routes, useLocation } from 'react-router-dom';
import { isHomeView } from './lib/utils/view-utils';
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';
import Catalog from './views/catalog';
@@ -21,9 +19,6 @@ const App = () => {
const location = useLocation();
const isInHomeView = isHomeView(location.pathname);
const subplebbitAddresses = useDefaultSubplebbitAddresses();
const { subplebbits } = useSubplebbits({ subplebbitAddresses });
// add theme className to body so it can set the correct body background in index.css
const [theme] = useTheme();
useEffect(() => {
@@ -40,7 +35,7 @@ const App = () => {
const boardLayout = (
<>
<BoardNav subplebbits={subplebbits} />
<BoardNav />
<BoardBanner />
<MobileBoardButtons />
<PostForm />
@@ -53,7 +48,7 @@ const App = () => {
return (
<div className={`${styles.app} ${isInHomeView ? 'yotsuba' : theme}`}>
<Routes>
<Route path='/' element={<Home subplebbits={subplebbits} />} />
<Route path='/' element={<Home />} />
<Route element={boardLayout}>
<Route path='/p/:subplebbitAddress' element={<Board />} />
<Route path='/p/:subplebbitAddress/c/:commentCid' element={<PostPage />} />
+1 -1
View File
@@ -3,7 +3,7 @@ import { useSubplebbit } from '@plebbit/plebbit-react-hooks';
import { useState } from 'react';
import styles from './board-banner.module.css';
const totalBanners = 52;
const totalBanners = 53;
const ImageBanner = () => {
const [imagePath] = useState(() => {
+19 -23
View File
@@ -1,15 +1,15 @@
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Subplebbit } from '@plebbit/plebbit-react-hooks';
import { isCatalogView } from '../../lib/utils/view-utils';
import styles from './board-nav.module.css';
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
interface BoardNavProps {
subplebbits: (Subplebbit | undefined)[];
currentSubplebbit?: string | undefined;
subplebbitAddresses: string[];
subplebbitAddress?: string;
}
const BoardNavDesktop = ({ subplebbits }: BoardNavProps) => {
const BoardNavDesktop = ({ subplebbitAddresses }: BoardNavProps) => {
const { t } = useTranslation();
const isInCatalogView = isCatalogView(useLocation().pathname, useParams());
@@ -17,16 +17,12 @@ const BoardNavDesktop = ({ subplebbits }: BoardNavProps) => {
<div className={styles.boardNavDesktop}>
<span className={styles.boardList}>
[
{subplebbits.map((subplebbit: any, index: number) => {
const address = subplebbit?.address || '';
const title = subplebbit?.title || '';
{subplebbitAddresses.map((address: string, index: number) => {
return (
<span key={index}>
{index === 0 ? null : ' '}
<Link to={`/p/${address}${isInCatalogView ? '/catalog' : ''}`} title={title || ''}>
{address.includes('.') ? address : title || address.slice(0, 10).concat('...')}
</Link>
{index !== subplebbits.length - 1 ? ' /' : null}
<Link to={`/p/${address}${isInCatalogView ? '/catalog' : ''}`}>{address.includes('.') ? address : address.slice(0, 10).concat('...')}</Link>
{index !== address.length - 1 ? ' /' : null}
</span>
);
})}
@@ -39,24 +35,23 @@ const BoardNavDesktop = ({ subplebbits }: BoardNavProps) => {
);
};
const BoardNavMobile = ({ subplebbits, currentSubplebbit }: BoardNavProps) => {
const BoardNavMobile = ({ subplebbitAddresses, subplebbitAddress }: BoardNavProps) => {
const { t } = useTranslation();
const navigate = useNavigate();
const displaySubplebbitAddress = currentSubplebbit && currentSubplebbit.length > 30 ? currentSubplebbit.slice(0, 30).concat('...') : currentSubplebbit;
const displaySubplebbitAddress = subplebbitAddress && subplebbitAddress.length > 30 ? subplebbitAddress.slice(0, 30).concat('...') : subplebbitAddress;
const currentSubplebbitIsInList = subplebbits.some((subplebbit: any) => subplebbit?.address === currentSubplebbit);
const currentSubplebbitIsInList = subplebbitAddresses.some((address: string) => address === subplebbitAddress);
const isInCatalogView = isCatalogView(useLocation().pathname, useParams());
const boardSelect = (
<select value={currentSubplebbit || 'all'} onChange={(e) => navigate(`/p/${e.target.value}${isInCatalogView ? '/catalog' : ''}`)}>
{!currentSubplebbitIsInList && currentSubplebbit && <option value={currentSubplebbit}>{displaySubplebbitAddress}</option>}
<select value={subplebbitAddress || 'all'} onChange={(e) => navigate(`/p/${e.target.value}${isInCatalogView ? '/catalog' : ''}`)}>
{!currentSubplebbitIsInList && subplebbitAddress && <option value={subplebbitAddress}>{displaySubplebbitAddress}</option>}
<option value='all'>{t('all')}</option>
<option value='subscriptions'>{t('subscriptions')}</option>
{subplebbits.map((subplebbit: any, index: number) => {
{subplebbitAddresses.map((subplebbit: any, index: number) => {
const address = subplebbit?.address || '';
const title = subplebbit?.title || '';
const subplebbitAddress = address?.includes('.') ? address : title || address?.slice(0, 10).concat('...');
const subplebbitAddress = address?.includes('.') ? address : address?.slice(0, 10).concat('...');
return (
<option key={index} value={address}>
{subplebbitAddress}
@@ -80,12 +75,13 @@ const BoardNavMobile = ({ subplebbits, currentSubplebbit }: BoardNavProps) => {
);
};
const BoardNav = ({ subplebbits }: BoardNavProps) => {
const { subplebbitAddress: address } = useParams();
const BoardNav = () => {
const subplebbitAddresses = useDefaultSubplebbitAddresses();
const { subplebbitAddress } = useParams();
return (
<>
<BoardNavDesktop subplebbits={subplebbits} />
<BoardNavMobile subplebbits={subplebbits} currentSubplebbit={address} />
<BoardNavDesktop subplebbitAddresses={subplebbitAddresses} />
<BoardNavMobile subplebbitAddresses={subplebbitAddresses} subplebbitAddress={subplebbitAddress} />
</>
);
};
+38 -7
View File
@@ -1,4 +1,4 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import { useTranslation } from 'react-i18next';
import { Comment } from '@plebbit/plebbit-react-hooks';
@@ -29,7 +29,13 @@ const CatalogPostMedia = ({ commentMediaInfo, link }: { commentMediaInfo: any; l
return thumbnailComponent;
};
const CatalogPost = ({ post }: { post: Comment }) => {
interface CatalogPostProps {
post: Comment;
openMenu: boolean;
toggleMenu: () => void;
}
const CatalogPost = ({ openMenu, post, toggleMenu }: CatalogPostProps) => {
const { t } = useTranslation();
const { cid, content, isDescription, isRules, link, linkHeight, linkWidth, locked, pinned, replyCount, subplebbitAddress, title } = post || {};
const commentMediaInfo = getCommentMediaInfo(post);
@@ -65,7 +71,18 @@ const CatalogPost = ({ post }: { post: Comment }) => {
</div>
);
const [menuBtnRotated, setMenuBtnRotated] = useState(false);
useEffect(() => {
const handlePageClick = () => {
if (openMenu) {
toggleMenu();
}
};
document.addEventListener('click', handlePageClick);
return () => {
document.removeEventListener('click', handlePageClick);
};
}, [openMenu, toggleMenu]);
return (
<div className={styles.post}>
@@ -93,8 +110,11 @@ const CatalogPost = ({ post }: { post: Comment }) => {
<span
className={styles.postMenuBtn}
title='Thread Menu'
onClick={() => setMenuBtnRotated(!menuBtnRotated)}
style={{ transform: menuBtnRotated ? 'rotate(90deg)' : 'rotate(0deg)' }}
onClick={(e) => {
e.stopPropagation();
toggleMenu();
}}
style={{ transform: openMenu ? 'rotate(90deg)' : 'rotate(0deg)' }}
>
</span>
@@ -116,8 +136,19 @@ interface CatalogRowProps {
}
const CatalogRow = ({ row }: CatalogRowProps) => {
const posts = row.map((post, index) => <CatalogPost key={index} post={post} />);
return <div className={styles.row}>{posts}</div>;
const [postWithMenuOpen, setPostWithMenuOpen] = useState<number | null>(null);
const handleToggleMenu = (index: number) => {
setPostWithMenuOpen(postWithMenuOpen === index ? null : index);
};
return (
<div className={styles.row}>
{row.map((post, index) => (
<CatalogPost key={index} post={post} openMenu={postWithMenuOpen === index} toggleMenu={() => handleToggleMenu(index)} />
))}
</div>
);
};
export default CatalogRow;
@@ -22,6 +22,14 @@
align-items: center;
}
.thumbnailSmall span, .thumbnailSmall a {
width: 100%;
display: inline-block;
word-wrap: break-word;
text-align: center;
cursor: pointer;
}
.thumbnail {
float: left;
}
+39 -20
View File
@@ -1,13 +1,14 @@
import React from 'react';
import styles from './comment-media.module.css';
import { CommentMediaInfo } from '../../lib/utils/media-utils';
import { CommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
import Embed from '../embed';
import Embed, { canEmbed } from '../embed';
import { useTranslation } from 'react-i18next';
import useWindowWidth from '../../hooks/use-window-width';
import { getHostname } from '../../lib/utils/url-utils';
interface MediaProps {
commentMediaInfo?: CommentMediaInfo;
isMobile: boolean;
isOutOfFeed?: boolean; // virtuoso wrapper unneeded
isReply: boolean;
linkHeight?: number;
@@ -35,10 +36,11 @@ const ThumbnailSmall = ({ style, children, thumbnailSmallPadding }: ThumbnailPro
</span>
);
const Thumbnail = ({ commentMediaInfo, isMobile, isOutOfFeed, isReply, linkHeight, linkWidth, setShowThumbnail }: MediaProps) => {
const Thumbnail = ({ commentMediaInfo, isOutOfFeed, isReply, linkHeight, linkWidth, setShowThumbnail }: MediaProps) => {
const { patternThumbnailUrl, thumbnail, type, url } = commentMediaInfo || {};
let displayWidth, displayHeight;
const isMobile = useWindowWidth() < 640;
const maxThumbnailSize = isMobile || isReply ? 125 : 250;
if (linkWidth && linkHeight) {
@@ -51,8 +53,8 @@ const Thumbnail = ({ commentMediaInfo, isMobile, isOutOfFeed, isReply, linkHeigh
}
if (type === 'audio') {
displayWidth = '250px';
displayHeight = '75px';
displayWidth = '100%';
displayHeight = '100%';
}
if (isOutOfFeed) {
@@ -63,6 +65,7 @@ const Thumbnail = ({ commentMediaInfo, isMobile, isOutOfFeed, isReply, linkHeigh
let thumbnailComponent: React.ReactNode = null;
const iframeThumbnail = patternThumbnailUrl || thumbnail;
const gifFrameUrl = useFetchGifFirstFrame(type === 'gif' ? url : undefined);
const hasThumbnail = getHasThumbnail(commentMediaInfo, url);
if (type === 'image') {
thumbnailComponent = <img src={url} alt='' onClick={() => setShowThumbnail(false)} />;
@@ -81,18 +84,31 @@ const Thumbnail = ({ commentMediaInfo, isMobile, isOutOfFeed, isReply, linkHeigh
const thumbnailSmallPadding = isMobile ? styles.thumbnailMobile : styles.thumbnailReplyDesktop;
const thumbnailDimensions = { '--width': displayWidth, '--height': displayHeight } as React.CSSProperties;
const linkWithoutThumbnail = url && new URL(url);
return isMobile || isReply ? (
<ThumbnailSmall style={thumbnailDimensions} thumbnailSmallPadding={thumbnailSmallPadding}>
{thumbnailComponent}
{isMobile &&
!hasThumbnail &&
linkWithoutThumbnail &&
(canEmbed(linkWithoutThumbnail) ? (
<span onClick={() => setShowThumbnail(false)}>{getHostname(url)}</span>
) : (
<a href={url} target='_blank' rel='noreferrer'>
{getHostname(url) || (url.length > 30 ? url.slice(0, 30) + '...' : url)}
</a>
))}
</ThumbnailSmall>
) : (
<ThumbnailBig style={thumbnailDimensions}>{thumbnailComponent}</ThumbnailBig>
);
};
const Media = ({ commentMediaInfo, isMobile, isReply, setShowThumbnail }: MediaProps) => {
const Media = ({ commentMediaInfo, isReply, setShowThumbnail }: MediaProps) => {
const { t } = useTranslation();
const { thumbnail, type, url } = commentMediaInfo || {};
const isMobile = useWindowWidth() < 640;
const mediaClass = isMobile ? styles.mediaMobile : isReply ? styles.mediaDesktopReply : styles.mediaDesktopOp;
return (
@@ -127,23 +143,26 @@ const Media = ({ commentMediaInfo, isMobile, isReply, setShowThumbnail }: MediaP
);
};
const CommentMedia = ({ commentMediaInfo, isMobile, isOutOfFeed, isReply, linkHeight, linkWidth, showThumbnail, setShowThumbnail }: MediaProps) => {
const CommentMedia = ({ commentMediaInfo, isOutOfFeed, isReply, linkHeight, linkWidth, showThumbnail, setShowThumbnail }: MediaProps) => {
const isMobile = useWindowWidth() < 640;
const { type, url } = commentMediaInfo || {};
return (
<span className={styles.content}>
<span className={`${showThumbnail ? styles.show : styles.hide} ${styles.thumbnail}`}>
<Thumbnail
commentMediaInfo={commentMediaInfo}
isMobile={isMobile}
isOutOfFeed={isOutOfFeed}
isReply={isReply}
linkHeight={linkHeight}
linkWidth={linkWidth}
showThumbnail={showThumbnail}
setShowThumbnail={setShowThumbnail}
/>
{isMobile && commentMediaInfo?.type && <div className={styles.fileInfo}>{commentMediaInfo.type}</div>}
{url && (
<Thumbnail
commentMediaInfo={commentMediaInfo}
isOutOfFeed={isOutOfFeed}
isReply={isReply}
linkHeight={linkHeight}
linkWidth={linkWidth}
showThumbnail={showThumbnail}
setShowThumbnail={setShowThumbnail}
/>
)}
{isMobile && type && <div className={styles.fileInfo}>{type}</div>}
</span>
{!showThumbnail && <Media commentMediaInfo={commentMediaInfo} isMobile={isMobile} isReply={isReply} setShowThumbnail={setShowThumbnail} />}
{!showThumbnail && <Media commentMediaInfo={commentMediaInfo} isReply={isReply} setShowThumbnail={setShowThumbnail} />}
</span>
);
};
+11
View File
@@ -67,4 +67,15 @@
width: 100% !important;
height: 50vh !important;
}
}
.soundcloudEmbed {
height: 166px; /* default height of soundcloud embeds */
width: 700px;
}
@media (max-width: 640px) {
.soundcloudEmbed {
width: 100% !important;
}
}
+22
View File
@@ -37,6 +37,9 @@ const Embed = ({ url }: EmbedProps) => {
if (spotifyHosts.has(parsedUrl.host)) {
return <SpotifyEmbed parsedUrl={parsedUrl} />;
}
if (soundcloudHosts.has(parsedUrl.host)) {
return <SoundcloudEmbed parsedUrl={parsedUrl} />;
}
};
interface EmbedComponentProps {
@@ -258,6 +261,24 @@ const SpotifyEmbed = ({ parsedUrl }: EmbedComponentProps) => {
);
};
const soundcloudHosts = new Set(['soundcloud.com', 'www.soundcloud.com', 'on.soundcloud.com', 'api.soundcloud.com', 'w.soundcloud.com']);
// not officially documented https://stackoverflow.com/questions/20870270/how-to-get-soundcloud-embed-code-by-soundcloud-com-url
const SoundcloudEmbed = ({ parsedUrl }: EmbedComponentProps) => {
return (
<iframe
className={styles.soundcloudEmbed}
height='100%'
width='100%'
referrerPolicy='no-referrer'
allow='accelerometer; encrypted-media; gyroscope; picture-in-picture; web-share'
allowFullScreen
title={parsedUrl.href}
src={`https://w.soundcloud.com/player/?url=${parsedUrl.href}`}
/>
);
};
const canEmbedHosts = new Set<string>([
...youtubeHosts,
...xHosts,
@@ -267,6 +288,7 @@ const canEmbedHosts = new Set<string>([
...instagramHosts,
...odyseeHosts,
...bitchuteHosts,
...soundcloudHosts,
...streamableHosts,
...spotifyHosts,
]);
+1 -3
View File
@@ -55,12 +55,10 @@ const Markdown = ({ content }: MarkdownProps) => {
remarkPlugins.push([blockquoteToGreentext]);
const doubleNewlineContent = content?.replace(/\n/g, '&nbsp;\n\n');
return (
<span className={styles.markdown}>
<ReactMarkdown
children={doubleNewlineContent}
children={content}
remarkPlugins={remarkPlugins}
rehypePlugins={[[rehypeSanitize, customSchema]]}
components={{
+35 -15
View File
@@ -12,6 +12,7 @@ import useWindowWidth from '../../hooks/use-window-width';
import styles from './post.module.css';
import Markdown from '../markdown';
import CommentMedia from '../comment-media';
import { canEmbed } from '../embed';
interface PostProps {
index?: number;
@@ -35,6 +36,7 @@ const PostDesktop = ({ post, roles, showAllReplies }: PostProps) => {
const displayContent = content && !isInPostPage && content.length > 1000 ? content?.slice(0, 1000) + '(...)' : content;
const commentMediaInfo = getCommentMediaInfo(post);
const { type, url } = commentMediaInfo || {};
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
const [showThumbnail, setShowThumbnail] = useState(true);
@@ -56,15 +58,15 @@ const PostDesktop = ({ post, roles, showAllReplies }: PostProps) => {
<span className={`${styles.hideButton} ${styles.hideThread}`} />
</span>
)}
{commentMediaInfo?.url && (
{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 href={url} target='_blank' rel='noopener noreferrer'>
{url.length > 30 ? url.slice(0, 30) + '...' : url}
</a>{' '}
({commentMediaInfo?.type})
{!showThumbnail && (commentMediaInfo?.type === 'iframe' || commentMediaInfo?.type === 'video' || commentMediaInfo?.type === 'audio') && (
({type})
{!showThumbnail && (type === 'iframe' || type === 'video' || type === 'audio') && (
<span>
{' '}
[
@@ -74,11 +76,20 @@ const PostDesktop = ({ post, roles, showAllReplies }: PostProps) => {
]
</span>
)}
{showThumbnail && !hasThumbnail && (
<span>
{' '}
[
<span className={styles.closeMedia} onClick={() => setShowThumbnail(false)}>
{t('open')}
</span>
]
</span>
)}
</div>
{hasThumbnail && (
{(hasThumbnail || (!hasThumbnail && !showThumbnail)) && (
<CommentMedia
commentMediaInfo={commentMediaInfo}
isMobile={false}
isOutOfFeed={isDescription || isRules} // virtuoso wrapper unneeded
isReply={false}
linkHeight={linkHeight}
@@ -182,6 +193,8 @@ const ReplyDesktop = ({ reply, roles }: PostProps) => {
const authorRole = roles?.[address]?.role;
const commentMediaInfo = getCommentMediaInfo(reply);
const { type, url } = commentMediaInfo || {};
const embedUrl = url && new URL(url);
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
const [showThumbnail, setShowThumbnail] = useState(true);
@@ -231,14 +244,14 @@ const ReplyDesktop = ({ reply, roles }: PostProps) => {
</span>
</span>
</div>
{link && (
{url && (
<div className={styles.file}>
<div className={styles.fileText}>
{t('link')}:{' '}
<a href={link} target='_blank' rel='noopener noreferrer'>
{link.length > 30 ? link?.slice(0, 30) + '...' : link}
</a>
{!showThumbnail && (commentMediaInfo?.type === 'iframe' || commentMediaInfo?.type === 'video' || commentMediaInfo?.type === 'audio') && (
{!showThumbnail && (type === 'iframe' || type === 'video' || type === 'audio') && (
<span>
{' '}
[
@@ -248,11 +261,20 @@ const ReplyDesktop = ({ reply, roles }: PostProps) => {
]
</span>
)}
{showThumbnail && !hasThumbnail && embedUrl && canEmbed(embedUrl) && (
<span>
{' '}
[
<span className={styles.closeMedia} onClick={() => setShowThumbnail(false)}>
{t('open')}
</span>
]
</span>
)}
</div>
{hasThumbnail && (
{(hasThumbnail || (!hasThumbnail && !showThumbnail)) && (
<CommentMedia
commentMediaInfo={commentMediaInfo}
isMobile={false}
isReply={true}
linkHeight={linkHeight}
linkWidth={linkWidth}
@@ -344,10 +366,9 @@ const PostMobile = ({ post, roles, showAllReplies }: PostProps) => {
)}
</span>
</div>
{hasThumbnail && (
{(hasThumbnail || link) && (
<CommentMedia
commentMediaInfo={commentMediaInfo}
isMobile={true}
isOutOfFeed={isDescription || isRules} // virtuoso wrapper unneeded
isReply={false}
linkHeight={linkHeight}
@@ -427,10 +448,9 @@ const ReplyMobile = ({ reply, roles }: PostProps) => {
<span className={styles.replyToPost}>{shortCid}</span>
</span>
</div>
{hasThumbnail && (
{(hasThumbnail || link) && (
<CommentMedia
commentMediaInfo={commentMediaInfo}
isMobile={true}
isReply={false}
linkHeight={linkHeight}
linkWidth={linkWidth}
+3 -3
View File
@@ -12,7 +12,7 @@ import SubplebbitRules from '../../components/subplebbit-rules';
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
const Subplebbit = () => {
const Board = () => {
const { t } = useTranslation();
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
const subplebbitAddresses = useMemo(() => [subplebbitAddress], [subplebbitAddress]) as string[];
@@ -58,7 +58,7 @@ const Subplebbit = () => {
return (
<div className={styles.content}>
{createdAt && (
{feed.length > 0 && (
<>
{rules && rules.length > 0 && <SubplebbitRules subplebbitAddress={subplebbitAddress} createdAt={createdAt} rules={rules} />}
{description && description.length > 0 && (
@@ -82,4 +82,4 @@ const Subplebbit = () => {
);
};
export default Subplebbit;
export default Board;
+20 -26
View File
@@ -12,60 +12,54 @@ import styles from './catalog.module.css';
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
const useFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolean, subplebbit: Subplebbit) => {
const modifiedFeed = useMemo(() => {
const { t } = useTranslation();
const { address, createdAt, description, rules, shortAddress, suggested, title } = subplebbit || {};
const { avatarUrl } = suggested || {};
const feedWithDescriptionAndRules = useMemo(() => {
if (!isFeedLoaded) {
return []; // prevent rules and description from appearing while feed is loading
}
if (!subplebbit?.description && !subplebbit?.rules) {
if (!description && !rules) {
return feed;
}
const _feed = [...feed];
if (subplebbit?.description) {
if (description && description.length > 0) {
_feed.unshift({
isDescription: true,
subplebbitAddress: subplebbit?.address,
timestamp: subplebbit?.createdAt,
subplebbitAddress: address,
timestamp: createdAt,
author: { displayName: '## Board Mods' },
content: subplebbit?.description,
link: subplebbit?.suggested?.avatarUrl,
title: 'Welcome to ' + (subplebbit?.title || `p/${subplebbit?.shortAddress}`),
content: description,
link: avatarUrl,
title: 'Welcome to ' + (title || `p/${shortAddress}`),
pinned: true,
locked: true,
});
}
if (subplebbit?.rules) {
if (rules && rules.length > 0) {
_feed.unshift({
isRules: true,
subplebbitAddress: subplebbit?.address,
timestamp: subplebbit?.createdAt,
subplebbitAddress: address,
timestamp: createdAt,
author: { displayName: '## Board Mods' },
content: subplebbit?.rules.map((rule: string, index: number) => `${index + 1}. ${rule}`).join('\n'),
content: rules.map((rule: string, index: number) => `${index + 1}. ${rule}`).join('\n'),
title: 'Rules',
pinned: true,
locked: true,
});
}
return _feed;
}, [
feed,
subplebbit?.description,
subplebbit?.rules,
subplebbit?.address,
isFeedLoaded,
subplebbit?.createdAt,
subplebbit?.title,
subplebbit?.shortAddress,
subplebbit?.suggested?.avatarUrl,
]);
}, [feed, description, rules, address, isFeedLoaded, createdAt, title, shortAddress, avatarUrl]);
// Memoize rows calculation, ensuring it updates on changes to the modified feed or column count
const rows = useMemo(() => {
const rows = [];
for (let i = 0; i < modifiedFeed.length; i += columnCount) {
rows.push(modifiedFeed.slice(i, i + columnCount));
for (let i = 0; i < feedWithDescriptionAndRules.length; i += columnCount) {
rows.push(feedWithDescriptionAndRules.slice(i, i + columnCount));
}
return rows;
}, [modifiedFeed, columnCount]);
}, [feedWithDescriptionAndRules, columnCount]);
return rows;
};
+9 -9
View File
@@ -2,12 +2,9 @@ import { useRef } from 'react';
import styles from './home.module.css';
import { Link, useNavigate } from 'react-router-dom';
import { Trans, useTranslation } from 'react-i18next';
import { Subplebbit } from '@plebbit/plebbit-react-hooks';
import { Subplebbit, useSubplebbits } from '@plebbit/plebbit-react-hooks';
import packageJson from '../../../package.json';
interface HomeProps {
subplebbits: (Subplebbit | undefined)[];
}
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
const isValidAddress = (address: string): boolean => {
if (address.includes('/') || address.includes('\\') || address.includes(' ')) {
@@ -69,7 +66,7 @@ const InfoBox = () => {
);
};
const Boards = ({ subplebbits }: HomeProps) => {
const Boards = ({ subplebbits }: { subplebbits: (Subplebbit | undefined)[] }) => {
const { t } = useTranslation();
return (
@@ -83,8 +80,8 @@ const Boards = ({ subplebbits }: HomeProps) => {
<h3>Default SFW</h3>
<div className={styles.list}>
{subplebbits
.filter((subplebbit): subplebbit is Subplebbit => subplebbit !== undefined)
.map((subplebbit) => {
.filter((subplebbit: Subplebbit | undefined): subplebbit is Subplebbit => subplebbit !== undefined)
.map((subplebbit: Subplebbit) => {
const address = subplebbit.address;
return (
<div className={styles.subplebbit} key={address}>
@@ -221,7 +218,10 @@ const Footer = () => {
);
};
const Home = ({ subplebbits }: HomeProps) => {
const Home = () => {
const subplebbitAddresses = useDefaultSubplebbitAddresses();
const { subplebbits } = useSubplebbits({ subplebbitAddresses });
return (
<div className={styles.content}>
<Link to='/'>