From 9291f3c9a5a6b94526951feef83fe8ab9e930c85 Mon Sep 17 00:00:00 2001 From: Tom Date: Wed, 3 May 2023 11:01:41 +0200 Subject: [PATCH] add functional subscriptions board, fixes --- src/App.js | 8 +- src/components/views/Board.jsx | 69 +- src/components/views/Catalog.jsx | 12 +- src/components/views/Subscriptions.jsx | 1023 ++++++++++++++++++++++++ src/components/views/Thread.jsx | 25 +- 5 files changed, 1103 insertions(+), 34 deletions(-) create mode 100644 src/components/views/Subscriptions.jsx diff --git a/src/App.js b/src/App.js index 94745d38..862ce2e5 100644 --- a/src/App.js +++ b/src/App.js @@ -7,12 +7,13 @@ 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 Home from './components/views/Home'; import Board from './components/views/Board'; -import Thread from './components/views/Thread'; import Catalog from './components/views/Catalog'; +import Home from './components/views/Home'; import NotFound from './components/views/NotFound'; import Pending from './components/views/Pending'; +import Subscriptions from './components/views/Subscriptions'; +import Thread from './components/views/Thread'; import CaptchaModal from './components/CaptchaModal'; import { importAll } from './components/ImageBanner'; import preloadImages from './utils/preloadImages'; @@ -161,6 +162,9 @@ export default function App() { }> } /> + }> + } /> + } /> diff --git a/src/components/views/Board.jsx b/src/components/views/Board.jsx index eb4791ce..838c2d04 100644 --- a/src/components/views/Board.jsx +++ b/src/components/views/Board.jsx @@ -3,7 +3,7 @@ import { Helmet } from 'react-helmet-async'; import { Link, useNavigate, useParams } from 'react-router-dom'; import { Tooltip } from 'react-tooltip'; import { Virtuoso } from 'react-virtuoso'; -import { useAccount, useAccountComments, useFeed, usePublishComment } from '@plebbit/plebbit-react-hooks'; +import { useAccount, useAccountComments, useFeed, usePublishComment, useSubscribe } 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'; @@ -23,6 +23,7 @@ import handleQuoteClick from '../../utils/handleQuoteClick'; import handleStyleChange from '../../utils/handleStyleChange'; import useClickForm from '../../hooks/useClickForm'; import useError from '../../hooks/useError'; +import useSuccess from '../../hooks/useSuccess'; import packageJson from '../../../package.json' const {version} = packageJson @@ -47,6 +48,8 @@ const Board = () => { showPostFormLink, } = useGeneralStore(state => state); + const account = useAccount(); + const nameRef = useRef(); const subjectRef = useRef(); const commentRef = useRef(); @@ -60,10 +63,12 @@ const Board = () => { const [selectedFeed, setSelectedFeed] = useState(feed); const { subplebbitAddress } = useParams(); - const [errorMessage, setErrorMessage] = useState(null); - useError(errorMessage, [errorMessage]); + const { subscribed, subscribe, unsubscribe } = useSubscribe({subplebbitAddress: selectedAddress}); - const account = useAccount(); + const [errorMessage, setErrorMessage] = useState(null); + const [successMessage, setSuccessMessage] = useState(null); + useError(errorMessage, [errorMessage]); + useSuccess(successMessage, [successMessage]); const [triggerPublishComment, setTriggerPublishComment] = useState(false); @@ -304,12 +309,33 @@ const Board = () => { // mobile navbar board select functionality const handleSelectChange = (event) => { const selected = event.target.value; + + if (selected === 'subscriptions') { + navigate(`/profile/p/subscriptions`); + return; + } + const selectedTitle = defaultSubplebbits.find((subplebbit) => subplebbit.address === selected).title; setSelectedTitle(selectedTitle); setSelectedAddress(selected); navigate(`/p/${selected}`); }; + const handleSubscribe = async () => { + try { + if (subscribed === false) { + await subscribe(selectedAddress); + setSuccessMessage(`Subscribed to ${selectedTitle ?? selectedAddress}`); + + } else if (subscribed === true) { + await unsubscribe(selectedAddress); + setSuccessMessage(`Unsubscribed from ${selectedTitle ?? selectedAddress}`); + } + + } catch (error) { + setErrorMessage(error); + } + }; return ( <> @@ -329,7 +355,7 @@ const Board = () => { <> [ - {}}>Subscriptions + Subscriptions ] [ {defaultSubplebbits.map((subplebbit, index) => ( @@ -362,7 +388,7 @@ const Board = () => { Board   + {defaultSubplebbits.map(subplebbit => ( @@ -497,7 +504,7 @@ const Catalog = () => { [ {}}>Subscriptions - ] [ + ]  {defaultSubplebbits.map((subplebbit, index) => ( @@ -510,7 +517,6 @@ const Catalog = () => { {index !== defaultSubplebbits.length - 1 ? " /" : null} ))} - ] [ + ] + [ + setIsSettingsOpen(true)}>Settings + ] + [ + handleStyleChange({target: {value: "Yotsuba"}} + )}>Home + ] + +
+
+ Board +   +   + +
+
+ setIsSettingsOpen(true)}>Settings +   + handleStyleChange({target: {value: "Yotsuba"}} + )}>Home +
+
+
 
+
 
+ + +
+ <> +
+ +
+ <> +
Subscriptions
+ {feed.length < 1 ? ( +
You haven't subscribed to any board yet.
+ ) : ( +
+ You have subscribed to {account.subscriptions.length} board{account.subscriptions.length > 1 ? "s" : null}. +
+ )} + + +
+ + +
+ + Style: +   + + +
+ [ + Catalog + ] +
+
+ + 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); + }} 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); + }} 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); + }} 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) + "(...)"} + + ) + } + )  + +
+
+ + {getDate(reply.timestamp)}  + c/ + {reply.shortCid ? ( + { + if (e.button === 2) return; + e.preventDefault(); + setIsReplyOpen(true); + setSelectedShortCid(reply.shortCid); + setSelectedParentCid(reply.cid); + }} 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: +   + + + + <> + + [ + {}}>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 Subscriptions; \ No newline at end of file diff --git a/src/components/views/Thread.jsx b/src/components/views/Thread.jsx index cc96f208..6e1e282b 100644 --- a/src/components/views/Thread.jsx +++ b/src/components/views/Thread.jsx @@ -250,6 +250,12 @@ const Thread = () => { // mobile navbar board select functionality const handleSelectChange = (event) => { const selected = event.target.value; + + if (selected === 'subscriptions') { + navigate(`/profile/p/subscriptions`); + return; + } + const selectedTitle = defaultSubplebbits.find((subplebbit) => subplebbit.address === selected).title; setSelectedTitle(selectedTitle); setSelectedAddress(selected); @@ -281,7 +287,7 @@ const Thread = () => { <> [ - {}}>Subscriptions + Subscriptions ] [ {defaultSubplebbits.map((subplebbit, index) => ( @@ -317,6 +323,7 @@ const Thread = () => { Board