diff --git a/src/App.js b/src/App.js index 6bf28ac8..79756c9c 100644 --- a/src/App.js +++ b/src/App.js @@ -7,6 +7,8 @@ import 'react-toastify/dist/ReactToastify.css'; import useGeneralStore from './hooks/stores/useGeneralStore'; import { GlobalStyle } from './components/styled/GlobalStyle.styled'; import { Toast } from './components/styled/Toast.styled'; +import All from './components/views/All'; +import AllCatalog from './components/views/AllCatalog'; import Board from './components/views/Board'; import Catalog from './components/views/Catalog'; import Home from './components/views/Home'; @@ -191,6 +193,12 @@ export default function App() { }> } /> + }> + } /> + + }> + } /> + } /> diff --git a/src/components/views/All.jsx b/src/components/views/All.jsx new file mode 100644 index 00000000..6db4b432 --- /dev/null +++ b/src/components/views/All.jsx @@ -0,0 +1,1105 @@ +import React, { Fragment, useEffect, useMemo, useState } from 'react'; +import { Helmet } from 'react-helmet-async'; +import { Link, useNavigate } from 'react-router-dom'; +import { Tooltip } from 'react-tooltip'; +import { Virtuoso } from 'react-virtuoso'; +import { useAccount, useAccountComments, useFeed, useSubplebbits } from '@plebbit/plebbit-react-hooks'; +import { flattenCommentsPages } from '@plebbit/plebbit-react-hooks/dist/lib/utils' +import { debounce } from 'lodash'; +import useGeneralStore from '../../hooks/stores/useGeneralStore'; +import { Container, NavBar, Header, Break, TopBar, BoardForm } from '../styled/Board.styled'; +import { Footer } from '../styled/Thread.styled'; +import ImageBanner from '../ImageBanner'; +import OfflineIndicator from '../OfflineIndicator'; +import Post from '../Post'; +import PostLoader from '../PostLoader'; +import ReplyModal from '../ReplyModal'; +import SettingsModal from '../SettingsModal'; +import findShortParentCid from '../../utils/findShortParentCid'; +import getCommentMediaInfo from '../../utils/getCommentMediaInfo'; +import getDate from '../../utils/getDate'; +import handleAddressClick from '../../utils/handleAddressClick'; +import handleImageClick from '../../utils/handleImageClick'; +import handleQuoteClick from '../../utils/handleQuoteClick'; +import handleStyleChange from '../../utils/handleStyleChange'; +import useError from '../../hooks/useError'; +import useFeedStateString from '../../hooks/useFeedStateString'; +import packageJson from '../../../package.json' +const {version} = packageJson + + +const All = () => { + const { + defaultSubplebbits, + isSettingsOpen, setIsSettingsOpen, + setSelectedAddress, + setSelectedParentCid, + setSelectedShortCid, + selectedStyle, + setSelectedThread, + setSelectedTitle, + } = useGeneralStore(state => state); + + const account = useAccount(); + + const [isReplyOpen, setIsReplyOpen] = useState(false); + const navigate = useNavigate(); + const [prevScrollPos, setPrevScrollPos] = useState(0); + const [visible, setVisible] = useState(true); + + const [errorMessage, setErrorMessage] = useState(null); + useError(errorMessage, [errorMessage]); + + const addresses = defaultSubplebbits.map(subplebbit => subplebbit.address); + const { feed, hasMore, loadMore } = useFeed({subplebbitAddresses: addresses, sortType: 'new'}); + const {subplebbits} = useSubplebbits({subplebbitAddresses: addresses, sortType: 'new'}); + const [selectedFeed, setSelectedFeed] = useState(feed.sort((a, b) => b.timestamp - a.timestamp)); + + const stateString = useFeedStateString(subplebbits); + + const errorString = useMemo(() => { + for (const subplebbit of subplebbits) { + if (subplebbit?.updatingState !== 'failed') { + return + } + } + for (const subplebbit of subplebbits) { + if (subplebbit?.error) { + return `Failed fetching subplebbit: ${subplebbit?.error.toString().slice(0, 300)}` + } + } + }, [subplebbits]) + + useEffect(() => { + if (errorString) { + setErrorMessage(errorString) + } + }, [errorString]) + + + useEffect(() => { + setSelectedFeed(feed.sort((a, b) => b.timestamp - a.timestamp)); + }, [feed]); + + + const flattenedRepliesByThread = useMemo(() => { + return selectedFeed.reduce((acc, thread) => { + const replies = flattenCommentsPages(thread.replies); + acc[thread.cid] = replies; + return acc; + }, {}); + }, [selectedFeed]); + + + const allParentCids = useMemo(() => { + const allRepliesCids = Object.values(flattenedRepliesByThread).flatMap(replies => replies.map(reply => reply.cid)); + const allThreadCids = selectedFeed.map(thread => thread.cid); + return [...allThreadCids, ...allRepliesCids]; + }, [flattenedRepliesByThread, selectedFeed]); + + + const filter = useMemo(() => ({ + parentCids: allParentCids + }), [allParentCids]); + + + const { accountComments } = useAccountComments({ filter }); + + + const filteredRepliesByThread = useMemo(() => { + const maxRepliesPerThread = 5; + + const accountRepliesNotYetInCommentReplies = selectedFeed.reduce((acc, thread) => { + const replyCids = new Set(flattenedRepliesByThread[thread.cid].map(reply => reply.cid)); + acc[thread.cid] = accountComments.filter(accountReply => !replyCids.has(accountReply.cid) && accountReply.parentCid === thread.cid); + return acc; + }, {}); + + return selectedFeed.reduce((acc, thread) => { + const combinedReplies = [...flattenedRepliesByThread[thread.cid], ...accountRepliesNotYetInCommentReplies[thread.cid]].sort((a, b) => a.timestamp - b.timestamp); + acc[thread.cid] = { + displayedReplies: combinedReplies.slice(0, maxRepliesPerThread), + omittedCount: Math.max(combinedReplies.length - maxRepliesPerThread, 0), + }; + return acc; + }, {}); + }, [flattenedRepliesByThread, accountComments, selectedFeed]); + + + const pendingReplyCounts = useMemo(() => { + return selectedFeed.reduce((acc, thread) => { + const replyCids = new Set(flattenedRepliesByThread[thread.cid].map(reply => reply.cid)); + acc[thread.cid] = accountComments.filter(accountReply => !replyCids.has(accountReply.cid) && accountReply.parentCid === thread.cid).length; + return acc; + }, {}); + }, [flattenedRepliesByThread, accountComments, selectedFeed]); + + // mobile navbar scroll effect + useEffect(() => { + const debouncedHandleScroll = debounce(() => { + const currentScrollPos = window.pageYOffset; + setVisible(prevScrollPos > currentScrollPos || currentScrollPos < 10); + setPrevScrollPos(currentScrollPos); + }, 50); + + window.addEventListener('scroll', debouncedHandleScroll); + + return () => window.removeEventListener('scroll', debouncedHandleScroll); + }, [prevScrollPos, visible]); + + + const tryLoadMore = async () => { + try { + await loadMore(); + } catch (e) { + await new Promise(resolve => setTimeout(resolve, 1000)); + } + }; + + // desktop navbar board select functionality + const handleClickTitle = (title, address) => { + setSelectedTitle(title); + setSelectedAddress(address); + setSelectedFeed(feed.filter(feed => feed.title === title)); + }; + + // mobile navbar board select functionality + const handleSelectChange = (event) => { + const selected = event.target.value; + + if (selected === 'subscriptions') { + navigate(`/p/subscriptions`); + return; + } else if (selected === 'all') { + navigate(`/p/all`); + return; + } + + const selectedTitle = defaultSubplebbits?.find((subplebbit) => subplebbit.address === selected).title; + setSelectedTitle(selectedTitle); + setSelectedAddress(selected); + navigate(`/p/${selected}`); + }; + + + return ( + <> + + p/All - plebchan + + + setIsReplyOpen(false)} /> + setIsSettingsOpen(false)} /> + + <> + + [ + All +  /  + Subscriptions + ] [ + {defaultSubplebbits.map((subplebbit, index) => ( + + {index === 0 ? null : "\u00a0"} + handleClickTitle(subplebbit.title, subplebbit.address)} + >{subplebbit.title ? subplebbit.title : subplebbit.address} + {index !== defaultSubplebbits.length - 1 ? " /" : null} + + ))} + ] + + + [ + + ] + [ + setIsSettingsOpen(true)}>Settings + ] + [ + handleStyleChange({target: {value: "Yotsuba"}} + )}>Home + ] + +
+
+ Board +   +   + +
+
+ setIsSettingsOpen(true)}>Settings +   + handleStyleChange({target: {value: "Yotsuba"}} + )}>Home +
+
+
 
+
 
+ +
+
+ <> +
+ +
+ <> +
p/All
+
Default boards currently curated by devs
+ + +
+ + +
+ + Style: +   + + +
+ [ + Catalog + ] +
+ {feed.length > 0 ? ( + null + ) : ( +
+ {stateString} +
+ )} +
+ + Catalog + +
+
+ + +
+ { feed.length < 1 ? ( + null + ) : ( + { + const { displayedReplies, omittedCount } = filteredRepliesByThread[thread.cid] || {}; + const commentMediaInfo = getCommentMediaInfo(thread); + const fallbackImgUrl = "assets/filedeleted-res.gif"; + return ( + +
+
+
+
+
+ {commentMediaInfo?.url ? ( +
+ + {commentMediaInfo?.type === "webpage" ? ( +
+ + {thread.thumbnailUrl ? ( + {commentMediaInfo.type} e.target.src = fallbackImgUrl} /> + ) : null} + +
+ ) : null} + {commentMediaInfo?.type === "image" ? ( +
+ + {commentMediaInfo.type} e.target.src = fallbackImgUrl} /> + +
+ ) : null} + {commentMediaInfo?.type === "video" ? ( + + + ) : null} + {commentMediaInfo?.type === "audio" ? ( + + + ) : null} +
+ ) : null} + + {thread.title ? ( + thread.title.length > 75 ? + + + {thread.title.slice(0, 75) + " (...)"} + + + : + {thread.title} + ) + : null}  + {thread.author.displayName + ? thread.author.displayName.length > 20 + ? + + {thread.author.displayName.slice(0, 20) + " (...)"} + + + : + {thread.author.displayName} + : + Anonymous} +   + (u/ + handleAddressClick(thread.author.shortAddress)} + > + {thread.author.shortAddress} + ) +   + {getDate(thread.timestamp)} +   + + c/ + { + if (e.button === 2) return; + e.preventDefault(); + setIsReplyOpen(true); + setSelectedShortCid(thread.shortCid); + setSelectedParentCid(thread.cid); + setSelectedAddress(thread.subplebbitAddress); + }} title="Reply to this post">{thread.shortCid} + +  p/ + + {thread.subplebbitAddress.includes(".eth") ? thread.subplebbitAddress : ( + {thread.subplebbitAddress.slice(0, 10) + "(...)"} + )} + + +    + [ + setSelectedThread(thread.cid)} className="reply-link" >Reply + ] + +   + + + + {thread.content ? ( + thread.content?.length > 1000 ? + +
+ + (...) +

+ Post too long.  + setSelectedThread(thread.cid)} className="ttl-link">Click here +  to view.
+
+
+ :
+ +
) + : null} +
+
+
+ + {omittedCount > 0 ? ( + + + {omittedCount} post{omittedCount > 1 ? "s" : ""} omitted. Click  + setSelectedThread(thread.cid)} className="ttl-link">here +  to view. + + ) : null} + + {displayedReplies?.map((reply, index) => { + const replyMediaInfo = getCommentMediaInfo(reply); + const fallbackImgUrl = "assets/filedeleted-res.gif"; + const shortParentCid = findShortParentCid(reply.parentCid, selectedFeed); + return ( +
+
{'>>'}
+
+
+ + {reply.author.displayName + ? reply.author.displayName.length > 20 + ? + + {reply.author.displayName.slice(0, 20) + " (...)"} + + + : + {reply.author.displayName} + : + Anonymous} +   + handleAddressClick(reply.author.shortAddress)} + > + (u/ + {reply.author?.shortAddress ? + ( + + {reply.author?.shortAddress} + + ) : ( + + {account?.author?.address.slice(0, 10) + "(...)"} + + ) + } + ) + + +   + {getDate(reply.timestamp)} +   + + c/ + {reply.shortCid ? ( + { + if (e.button === 2) return; + e.preventDefault(); + setIsReplyOpen(true); + setSelectedShortCid(reply.shortCid); + setSelectedParentCid(reply.cid); + setSelectedAddress(thread.subplebbitAddress); + }} title="Reply to this post">{reply.shortCid} + ) : ( + Pending + )} +  p/ + + {thread.subplebbitAddress.includes(".eth") ? + (thread.subplebbitAddress) : + ( + {thread.subplebbitAddress.slice(0, 10) + "(...)"} + + )} + + +   + + +
+ {replyMediaInfo?.url ? ( +
+ + {replyMediaInfo?.type === "webpage" ? ( +
+ + {reply.thumbnailUrl ? ( + {replyMediaInfo.type} e.target.src = fallbackImgUrl} /> + ) : null} + +
+ ) : null} + {replyMediaInfo?.type === "image" ? ( +
+ + {replyMediaInfo.type} e.target.src = fallbackImgUrl} /> + +
+ ) : null} + {replyMediaInfo?.type === "video" ? ( + + + ) : null} + {replyMediaInfo?.type === "audio" ? ( + + + ) : null} +
+ ) : null} + {reply.content ? ( + reply.content?.length > 500 ? + +
+ {}} key={`r-pm-${index}`} className="quotelink" onClick={(event) => handleQuoteClick(reply, shortParentCid, thread.shortCid, event)}> + {`c/${shortParentCid}`}{shortParentCid === thread.shortCid ? " (OP)" : null} + + + (...) +

+ Comment too long.  + setSelectedThread(thread.cid)} className="ttl-link">Click here +  to view.
+
+
+ :
+ {}} key={`r-pm-${index}`} className="quotelink" onClick={(event) => handleQuoteClick(reply, shortParentCid, thread.shortCid, event)}> + {`c/${shortParentCid}`}{shortParentCid === thread.shortCid ? " (OP)" : null} + + +
) + : null} +
+
+ ) + })} +
+
+ {index === 0 ? ( +
+ ) : ( +
+ )} +
+
+
+ + + {thread.author.displayName + ? thread.author.displayName.length > 20 + ? + + {thread.author.displayName.slice(0, 20) + " (...)"} + + + : + {thread.author.displayName} + : + Anonymous} +   + handleAddressClick(thread.author.shortAddress)} + > + (u/ + + {thread.author.shortAddress} + + )  + +
+ {thread.title ? ( + thread.title.length > 30 ? + + + {thread.title.slice(0, 30) + " (...)"} + + + : + {thread.title} + ) + : null} +
+ + p/ + + {thread.subplebbitAddress.includes(".eth") ? + (thread.subplebbitAddress) : + ( + {thread.subplebbitAddress.slice(0, 10) + "(...)"} + + )} + + + + + {getDate(thread.timestamp)} +   + c/ + { + if (e.button === 2) return; + e.preventDefault(); + setIsReplyOpen(true); + setSelectedShortCid(thread.shortCid); + setSelectedParentCid(thread.cid); + setSelectedAddress(thread.subplebbitAddress); + }} title="Reply to this post">{thread.shortCid} + + +
+ {thread.link ? ( +
+ {commentMediaInfo?.url ? ( + commentMediaInfo.type === "webpage" ? ( +
+ + {thread.thumbnailUrl ? ( + thumbnail e.target.src = fallbackImgUrl} /> + ) : null} +
{commentMediaInfo?.type}
+
+
+ ) : commentMediaInfo.type === "image" ? ( +
+ + {commentMediaInfo.type} e.target.src = fallbackImgUrl} /> +
{commentMediaInfo?.type}
+
+
+ ) : commentMediaInfo.type === "video" ? ( + + + ) : commentMediaInfo.type === "audio" ? ( + + + ) : null + ) : null} +
+ ) : null} + {thread.content ? ( + thread.content?.length > 500 ? + +
+ + (...) +

+ Post too long.  + setSelectedThread(thread.cid)} className="ttl-link">Click here +  to view.
+
+
+ :
+ +
) + : null} +
+
+ { + (thread.replyCount + pendingReplyCounts[thread.cid]) === 0 ? + ("No replies") + : (thread.replyCount + pendingReplyCounts[thread.cid]) === 1 ? + ("1 reply") + : (thread.replyCount + pendingReplyCounts[thread.cid]) > 1 ? + ((thread.replyCount + pendingReplyCounts[thread.cid]) + " replies") + : null + } + setSelectedThread(thread.cid)} className="button-mobile" >View Thread +
+
+ {displayedReplies?.map((reply, index) => { + const replyMediaInfo = getCommentMediaInfo(reply); + const shortParentCid = findShortParentCid(reply.parentCid, selectedFeed); + return ( +
+
+
+ + + {reply.author.displayName + ? reply.author.displayName.length > 20 + ? + + {reply.author.displayName.slice(0, 20) + " (...)"} + + + : + {reply.author.displayName} + : + Anonymous} +   + handleAddressClick(reply.author.shortAddress)} + > + (u/ + {reply.author?.shortAddress ? + ( + + {reply.author?.shortAddress} + + ) : ( + + {account?.author?.address.slice(0, 8) + "(...)"} + + ) + } + )  + +
+
+ +  p/ + + {thread.subplebbitAddress.includes(".eth") ? + (thread.subplebbitAddress) : + ( + {thread.subplebbitAddress.slice(0, 10) + "(...)"} + + )} + + + + + {getDate(reply.timestamp)}  + c/ + {reply.shortCid ? ( + { + if (e.button === 2) return; + e.preventDefault(); + setIsReplyOpen(true); + setSelectedShortCid(reply.shortCid); + setSelectedParentCid(reply.cid); + setSelectedAddress(thread.subplebbitAddress); + }} title="Reply to this post">{reply.shortCid} + + ) : ( + Pending + )} + +
+ {reply.link ? ( +
+ {replyMediaInfo?.url ? ( + replyMediaInfo.type === "webpage" ? ( +
+ + {reply.thumbnailUrl ? ( + thumbnail e.target.src = fallbackImgUrl} /> + ) : null} +
{replyMediaInfo.type}
+
+
+ ) : replyMediaInfo.type === "image" ? ( +
+ + {replyMediaInfo.type} e.target.src = fallbackImgUrl} /> +
{replyMediaInfo.type}
+
+
+ ) : replyMediaInfo.type === "video" ? ( + + + ) : replyMediaInfo.type === "audio" ? ( + + + ) : null + ) : null} +
+ ) : null} + {reply.content ? ( + reply.content?.length > 500 ? + +
+ {}} key={`mob-r-pm-${index}`} className="quotelink" onClick={(event) => handleQuoteClick(reply, shortParentCid, thread.shortCid, event)}> + {`c/${shortParentCid}`}{shortParentCid === thread.shortCid ? " (OP)" : null} + + + (...) +

+ Comment too long.  + setSelectedThread(thread.cid)} className="ttl-link">Click here +  to view.
+
+
+ :
+ {}} key={`mob-r-pm-${index}`} className="quotelink" onClick={(event) => handleQuoteClick(reply, shortParentCid, thread.shortCid, event)}> + {`c/${shortParentCid}`}{shortParentCid === thread.shortCid ? " (OP)" : null} + + +
) + : null} + {reply.replyCount > 0 ? ( +
+ {reply.replies?.pages?.topAll.comments + .sort((a, b) => a.timestamp - b.timestamp) + .map((reply, index) => ( +
+ {}} + onClick={(event) => handleQuoteClick(reply, reply.shortCid, event)} className="quote-link"> + c/{reply.shortCid} +   +
+ ))} +
+ ) : null} +
+
+ )})} +
+
+ ); + }} + endReached={tryLoadMore} + useWindowScroll={true} + components={{ Footer: hasMore ? () => : null }} + /> + )} +
+
+
+ + + + Style: +   + + + + <> + + [ + All +  /  + Subscriptions + ] [ + + {defaultSubplebbits.map((subplebbit, index) => ( + + {index === 0 ? null : "\u00a0"} + handleClickTitle(subplebbit.title, subplebbit.address)} + >{subplebbit.title ? subplebbit.title : subplebbit.address} + {index !== defaultSubplebbits.length - 1 ? " /" : null} + + ))} + + [ + + ] + [ + setIsSettingsOpen(true)}>Settings + ] + [ + handleStyleChange({target: {value: "Yotsuba"}} + )}>Home + ] + + + +
+ plebchan v{version}. GPL-2.0 +
+
+ About +  •  + App +  •  + Twitter +  •  + Telegram +
+
+
+ + ); +} + +export default All; \ No newline at end of file diff --git a/src/components/views/AllCatalog.jsx b/src/components/views/AllCatalog.jsx new file mode 100644 index 00000000..9c0505aa --- /dev/null +++ b/src/components/views/AllCatalog.jsx @@ -0,0 +1,385 @@ +import React, { Fragment, useEffect, useState } from 'react'; +import { Helmet } from 'react-helmet-async'; +import InfiniteScroll from 'react-infinite-scroller'; +import { Link, useNavigate } from 'react-router-dom'; +import { Tooltip } from 'react-tooltip'; +import { useAccount, useFeed, useSubplebbits } from '@plebbit/plebbit-react-hooks'; +import { debounce } from 'lodash'; +import useGeneralStore from '../../hooks/stores/useGeneralStore'; +import { Container, NavBar, Header, Break} from '../styled/Board.styled'; +import { Threads } from '../styled/Catalog.styled'; +import { TopBar, Footer } from '../styled/Thread.styled'; +import ImageBanner from '../ImageBanner'; +import OfflineIndicator from '../OfflineIndicator'; +import SettingsModal from '../SettingsModal'; +import getCommentMediaInfo from '../../utils/getCommentMediaInfo'; +import handleStyleChange from '../../utils/handleStyleChange'; +import useError from '../../hooks/useError'; +import useFeedStateString from '../../hooks/useFeedStateString'; +import packageJson from '../../../package.json' +const {version} = packageJson + + +const AllCatalog = () => { + const { + defaultSubplebbits, + isSettingsOpen, setIsSettingsOpen, + selectedAddress, setSelectedAddress, + selectedStyle, + setSelectedThread, + setSelectedTitle, + } = useGeneralStore(state => state); + + const account = useAccount(); + + const navigate = useNavigate(); + const [prevScrollPos, setPrevScrollPos] = useState(0); + const [visible, setVisible] = useState(true); + const addresses = defaultSubplebbits.map(subplebbit => subplebbit.address); + const { feed, hasMore, loadMore } = useFeed({subplebbitAddresses: addresses, sortType: 'new'}); + const {subplebbits} = useSubplebbits({subplebbitAddresses: addresses, sortType: 'new'}); + const [setSelectedFeed] = useState(feed.sort((a, b) => b.timestamp - a.timestamp)); + + const stateString = useFeedStateString(subplebbits); + + const [errorMessage] = useState(null); + useError(errorMessage, [errorMessage]); + + // mobile navbar scroll effect + useEffect(() => { + const debouncedHandleScroll = debounce(() => { + const currentScrollPos = window.pageYOffset; + setVisible(prevScrollPos > currentScrollPos || currentScrollPos < 10); + setPrevScrollPos(currentScrollPos); + }, 50); + + window.addEventListener('scroll', debouncedHandleScroll); + + return () => window.removeEventListener('scroll', debouncedHandleScroll); + }, [prevScrollPos, visible]); + + + const tryLoadMore = async () => { + try {loadMore()} + catch (e) + {await new Promise(resolve => setTimeout(resolve, 1000))} + }; + + // desktop navbar board select functionality + const handleClickTitle = (title, address) => { + setSelectedTitle(title); + setSelectedAddress(address); + setSelectedFeed(feed.filter(feed => feed.title === title)); + }; + + // mobile navbar board select functionality + const handleSelectChange = (event) => { + const selected = event.target.value; + + if (selected === 'subscriptions') { + navigate(`/p/subscriptions`); + return; + } else if (selected === 'all') { + navigate(`/p/all`); + return; + } + + const selectedTitle = defaultSubplebbits.find((subplebbit) => subplebbit.address === selected).title; + setSelectedTitle(selectedTitle); + setSelectedAddress(selected); + navigate(`/p/${selected}`); + }; + + + return ( + <> + + p/All - Catalog - plebchan + + + setIsSettingsOpen(false)} /> + + <> + + [ + All +  /  + Subscriptions + ] [ + {defaultSubplebbits.map((subplebbit, index) => ( + + {index === 0 ? null : "\u00a0"} + { + setSelectedTitle(subplebbit.title); + setSelectedAddress(subplebbit.address); + }} + >{subplebbit.title ? subplebbit.title : subplebbit.address} + {index !== defaultSubplebbits.length - 1 ? " /" : null} + + ))} + ] + + + [ + + ] + [ + setIsSettingsOpen(true)}>Settings + ] + [ + handleStyleChange({target: {value: "Yotsuba"}} + )}>Home + ] + +
+
+ Board +   +   + +
+
+ setIsSettingsOpen(true)}>Settings +   + handleStyleChange({target: {value: "Yotsuba"}} + )}>Home +
+
+
 
+
 
+ +
+
+ <> +
+ +
+
p/All
+
Default boards currently curated by devs
+ +
+ + +
+ + Style: +   + + +
+ [ + Return + ] +
+
+ + Return + +
+ {feed.length > 0 ? ( + null + ) : ( +
+ {stateString} +
+ )} +
+
+ + + { feed.length < 1 ? ( + null + ) : ( + + {feed.map((thread, index) => { + const commentMediaInfo = getCommentMediaInfo(thread); + const fallbackImgUrl = "assets/filedeleted-res.gif"; + return ( + setSelectedThread(thread.cid)}> +
+ {commentMediaInfo?.url ? ( + + {commentMediaInfo?.type === "webpage" ? ( + thread.thumbnailUrl ? ( + {commentMediaInfo.type} { + e.target.src = fallbackImgUrl + e.target.onerror = null; + }} /> + ) : null + ) : null} + {commentMediaInfo?.type === "image" ? ( + {commentMediaInfo.type} { + e.target.src = fallbackImgUrl + e.target.onerror = null;}} /> + ) : null} + {commentMediaInfo?.type === "video" ? ( + + ) : null} +
+ {(commentMediaInfo && ( + commentMediaInfo.type === 'image' || + commentMediaInfo.type === 'video' || + (commentMediaInfo.type === 'webpage' && + commentMediaInfo.thumbnail))) ? ( + // */ + + ) : ( + // */ + + ) } +
+
+ R: + {thread.replyCount} +
+
+ {thread.title ? `${thread.title}` : null} + {thread.content ? `: ${thread.content}` : null} +
+
+ + )})} +
+ )} +
+
+ + + + Style: +   + + + + <> + + [ + All +  /  + Subscriptions + ] [ + {defaultSubplebbits.map((subplebbit, index) => ( + + {index === 0 ? null : "\u00a0"} + handleClickTitle(subplebbit.title, subplebbit.address)} + >{subplebbit.title ? subplebbit.title : subplebbit.address} + {index !== defaultSubplebbits.length - 1 ? " /" : null} + + ))} + ] + + + [ + + ] + [ + setIsSettingsOpen(true)}>Settings + ] + [ + handleStyleChange({target: {value: "Yotsuba"}} + )}>Home + ] + + + +
+ plebchan v{version}. GPL-2.0 +
+
+ About +  •  + App +  •  + Twitter +  •  + Telegram +
+
+
+ + ); +} + +export default AllCatalog; \ No newline at end of file diff --git a/src/components/views/Board.jsx b/src/components/views/Board.jsx index 1a91aa86..7ec4b02c 100644 --- a/src/components/views/Board.jsx +++ b/src/components/views/Board.jsx @@ -339,6 +339,9 @@ const Board = () => { if (selected === 'subscriptions') { navigate(`/p/subscriptions`); return; + } else if (selected === 'all') { + navigate(`/p/all`); + return; } const selectedTitle = defaultSubplebbits.find((subplebbit) => subplebbit.address === selected).title; @@ -379,6 +382,8 @@ const Board = () => { <> [ + All +  /  Subscriptions ] [ {defaultSubplebbits.map((subplebbit, index) => ( @@ -412,6 +417,7 @@ const Board = () => { Board   + {defaultSubplebbits.map(subplebbit => (