diff --git a/src/App.js b/src/App.js index 862ce2e5..8f6a090a 100644 --- a/src/App.js +++ b/src/App.js @@ -13,6 +13,7 @@ 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 SubscriptionsCatalog from './components/views/SubscriptionsCatalog'; import Thread from './components/views/Thread'; import CaptchaModal from './components/CaptchaModal'; import { importAll } from './components/ImageBanner'; @@ -162,9 +163,12 @@ export default function App() { }> } /> - }> + }> } /> + }> + } /> + } /> diff --git a/src/components/views/Board.jsx b/src/components/views/Board.jsx index 3c07d644..c3338104 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, useSubscribe } from '@plebbit/plebbit-react-hooks'; +import { useAccount, useAccountComments, useFeed, usePublishComment, useSubplebbit, 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'; @@ -16,6 +16,7 @@ import PostLoader from '../PostLoader'; import ReplyModal from '../ReplyModal'; import SettingsModal from '../SettingsModal'; import findShortParentCid from '../../utils/findShortParentCid'; +import formatState from '../../utils/formatState'; import getCommentMediaInfo from '../../utils/getCommentMediaInfo'; import getDate from '../../utils/getDate'; import handleAddressClick from '../../utils/handleAddressClick'; @@ -63,6 +64,14 @@ const Board = () => { const { feed, hasMore, loadMore } = useFeed({subplebbitAddresses: [`${selectedAddress}`], sortType: 'new'}); const [selectedFeed, setSelectedFeed] = useState(feed); const { subplebbitAddress } = useParams(); + const subplebbit = useSubplebbit({subplebbitAddress: selectedAddress}); + + useEffect(() => { + if (subplebbit.error) { + const errorMessage = formatState(subplebbit.error); + setErrorMessage(errorMessage); + } + }, [subplebbit.error]); const { subscribed, subscribe, unsubscribe } = useSubscribe({subplebbitAddress: selectedAddress}); @@ -312,7 +321,7 @@ const Board = () => { const selected = event.target.value; if (selected === 'subscriptions') { - navigate(`/profile/p/subscriptions`); + navigate(`/p/subscriptions`); return; } @@ -322,6 +331,7 @@ const Board = () => { navigate(`/p/${selected}`); }; + const handleSubscribe = async () => { try { if (subscribed === false) { @@ -333,6 +343,7 @@ const Board = () => { setErrorMessage(error); } }; + return ( <> @@ -352,7 +363,7 @@ const Board = () => { <> [ - Subscriptions + Subscriptions ] [ {defaultSubplebbits.map((subplebbit, index) => ( @@ -505,7 +516,7 @@ const Board = () => { ) : (
- Fetching IPFS... + {formatState(subplebbit.state)}
)}
diff --git a/src/components/views/Catalog.jsx b/src/components/views/Catalog.jsx index 33bfb70a..8a709fc9 100644 --- a/src/components/views/Catalog.jsx +++ b/src/components/views/Catalog.jsx @@ -2,7 +2,7 @@ import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react import { Helmet } from 'react-helmet-async'; import InfiniteScroll from 'react-infinite-scroller'; import { Link, useNavigate, useParams } from 'react-router-dom'; -import { useFeed, usePublishComment, useSubscribe } from '@plebbit/plebbit-react-hooks'; +import { useFeed, usePublishComment, useSubplebbit, useSubscribe } from '@plebbit/plebbit-react-hooks'; import { debounce } from 'lodash'; import useGeneralStore from '../../hooks/stores/useGeneralStore'; import { Container, NavBar, Header, Break, PostForm, PostFormLink, PostFormTable } from '../styled/Board.styled'; @@ -12,6 +12,7 @@ import CatalogLoader from '../CatalogLoader'; import ImageBanner from '../ImageBanner'; import OfflineIndicator from '../OfflineIndicator'; import SettingsModal from '../SettingsModal'; +import formatState from '../../utils/formatState'; import getCommentMediaInfo from '../../utils/getCommentMediaInfo'; import handleStyleChange from '../../utils/handleStyleChange'; import useClickForm from '../../hooks/useClickForm'; @@ -49,6 +50,14 @@ const Catalog = () => { const [visible, setVisible] = useState(true); const { feed, hasMore, loadMore } = useFeed({subplebbitAddresses: [`${selectedAddress}`], sortType: 'new'}); const { subplebbitAddress } = useParams(); + const subplebbit = useSubplebbit({subplebbitAddress: selectedAddress}); + + useEffect(() => { + if (subplebbit.error) { + const errorMessage = formatState(subplebbit.error); + setErrorMessage(errorMessage); + } + }, [subplebbit.error]); const { subscribed, subscribe, unsubscribe } = useSubscribe({subplebbitAddress: selectedAddress}); @@ -226,7 +235,7 @@ const Catalog = () => { const selected = event.target.value; if (selected === 'subscriptions') { - navigate(`/profile/p/subscriptions`); + navigate(`/p/subscriptions`); return; } @@ -248,6 +257,7 @@ const Catalog = () => { } }; + return ( <> @@ -262,7 +272,7 @@ const Catalog = () => { <> [ - Subscriptions + Subscriptions ] [ {defaultSubplebbits.map((subplebbit, index) => ( @@ -418,7 +428,7 @@ const Catalog = () => { ) : (
- Fetching IPFS... + {formatState(subplebbit.state)}
)}
@@ -426,9 +436,6 @@ const Catalog = () => { Return
-
- {feed.length > 0 ? (null) : (Fetching IPFS...)} -

diff --git a/src/components/views/Home.jsx b/src/components/views/Home.jsx index 1cb10b23..04e66403 100644 --- a/src/components/views/Home.jsx +++ b/src/components/views/Home.jsx @@ -92,13 +92,13 @@ const Home = () => { {account?.subscriptions?.length > 0 ? ( -

Board Subscriptions

+

Subscriptions

You have subscribed to {account?.subscriptions?.length} board{account?.subscriptions?.length > 1 ? "s" : null}

  - [view all] + [view all]
{account?.subscriptions?.map((subscription, index) => ( <> diff --git a/src/components/views/Pending.jsx b/src/components/views/Pending.jsx index 01f041e5..b9911e05 100644 --- a/src/components/views/Pending.jsx +++ b/src/components/views/Pending.jsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import { Helmet } from 'react-helmet-async'; import { Link, useNavigate, useParams } from 'react-router-dom'; import { Tooltip } from 'react-tooltip'; @@ -35,11 +35,20 @@ const Pending = () => { const account = useAccount(); const comment = useAccountComment({commentIndex: index}); + const { publishingState, error } = useAccountComment({commentIndex: index}); const [visible] = useState(true); - const [errorMessage] = useState(null); + + const [errorMessage, setErrorMessage] = useState(null); useError(errorMessage, [errorMessage]); + useEffect(() => { + if (error) { + const errorMessage = formatState(error); + setErrorMessage(errorMessage); + } + }, [error]); + const navigate = useNavigate(); const [commentMediaInfo, setCommentMediaInfo] = useState(null); const fallbackImgUrl = "assets/filedeleted-res.gif"; @@ -169,7 +178,7 @@ const Pending = () => { Catalog ]
- {formatState(comment.state)} + {formatState(publishingState)}
diff --git a/src/components/views/Subscriptions.jsx b/src/components/views/Subscriptions.jsx index db292b93..5587b56c 100644 --- a/src/components/views/Subscriptions.jsx +++ b/src/components/views/Subscriptions.jsx @@ -1,6 +1,6 @@ import React, { Fragment, useEffect, useMemo, useState } from 'react'; import { Helmet } from 'react-helmet-async'; -import { Link, useNavigate, useParams } from 'react-router-dom'; +import { Link, useNavigate } from 'react-router-dom'; import { Tooltip } from 'react-tooltip'; import { Virtuoso } from 'react-virtuoso'; import { useAccount, useAccountComments, useFeed } from '@plebbit/plebbit-react-hooks'; @@ -52,8 +52,6 @@ const Subscriptions = () => { setSelectedFeed(feed.sort((a, b) => b.timestamp - a.timestamp)); }, [feed]); - const { subplebbitAddress } = useParams(); - const [errorMessage] = useState(null); useError(errorMessage, [errorMessage]); @@ -137,10 +135,6 @@ const Subscriptions = () => { setSelectedTitle(title); setSelectedAddress(address); setSelectedFeed(feed.filter(feed => feed.title === title)); - - if (subplebbitAddress === address) { - window.location.reload(); - } }; // mobile navbar board select functionality @@ -152,10 +146,6 @@ const Subscriptions = () => { navigate(`/p/${selected}`); }; - useEffect(() => { - console.log(feed); - }, [feed]); - return ( <> @@ -175,7 +165,7 @@ const Subscriptions = () => { <> [ - Subscriptions + Subscriptions ] [ {defaultSubplebbits.map((subplebbit, index) => ( @@ -200,7 +190,7 @@ const Subscriptions = () => { }>Create Board ] [ - setIsSettingsOpen(true)}>Settings + setIsSettingsOpen(true)}>Settings ] [ handleStyleChange({target: {value: "Yotsuba"}} @@ -225,7 +215,7 @@ const Subscriptions = () => { }>Create Board
- setIsSettingsOpen(true)}>Settings + setIsSettingsOpen(true)}>Settings   handleStyleChange({target: {value: "Yotsuba"}} )}>Home @@ -267,6 +257,16 @@ const Subscriptions = () => { +
+ [ + Catalog + ] +
+
+ + Catalog + +
@@ -1037,7 +1037,7 @@ const Subscriptions = () => { }>Create Board ] [ - setIsSettingsOpen(true)}>Settings + setIsSettingsOpen(true)}>Settings ] [ handleStyleChange({target: {value: "Yotsuba"}} diff --git a/src/components/views/SubscriptionsCatalog.jsx b/src/components/views/SubscriptionsCatalog.jsx new file mode 100644 index 00000000..d5b99b46 --- /dev/null +++ b/src/components/views/SubscriptionsCatalog.jsx @@ -0,0 +1,362 @@ +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 { useAccount, useFeed } 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 CatalogLoader from '../CatalogLoader'; +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 packageJson from '../../../package.json' +const {version} = packageJson + + +const SubscriptionsCatalog = () => { + 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 { feed, hasMore, loadMore } = useFeed({subplebbitAddresses: account?.subscriptions, sortType: 'new'}); + const [selectedFeed, setSelectedFeed] = useState(feed.sort((a, b) => b.timestamp - a.timestamp)); + + 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') { + return; + } + + const selectedTitle = defaultSubplebbits.find((subplebbit) => subplebbit.address === selected).title; + setSelectedTitle(selectedTitle); + setSelectedAddress(selected); + navigate(`/p/${selected}`); + }; + + + return ( + <> + + Subscriptions - Catalog - plebchan + + + setIsSettingsOpen(false)} /> + + <> + + [ + 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 +
+
+
 
+
 
+ +
+
+ <> +
+ +
+ <> +
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: +   + + +
+ [ + Return + ] +
+
+ + Return + +
+
+
+ + {selectedFeed.length < 1 ? ( + + ) : ( + + {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} + {/*
+ +
*/} +
+ R: + {thread.replyCount} +
+
+ {thread.title ? `${thread.title}` : null} + {thread.content ? `: ${thread.content}` : 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 SubscriptionsCatalog; \ No newline at end of file diff --git a/src/components/views/Thread.jsx b/src/components/views/Thread.jsx index 74a0b97b..ac49693b 100644 --- a/src/components/views/Thread.jsx +++ b/src/components/views/Thread.jsx @@ -253,7 +253,7 @@ const Thread = () => { const selected = event.target.value; if (selected === 'subscriptions') { - navigate(`/profile/p/subscriptions`); + navigate(`/p/subscriptions`); return; } @@ -288,7 +288,7 @@ const Thread = () => { <> [ - Subscriptions + Subscriptions ] [ {defaultSubplebbits.map((subplebbit, index) => ( diff --git a/src/utils/formatState.js b/src/utils/formatState.js index bfc4952b..4218972e 100644 --- a/src/utils/formatState.js +++ b/src/utils/formatState.js @@ -1,18 +1,27 @@ function formatState(str) { - return ( - str - .replace(/-/g, " ") - .split(" ") - .map((word) => { - word = word.toLowerCase(); - if (word === "ipfs" || word === "ipns") { - return word.toUpperCase(); - } else { - return word.charAt(0).toUpperCase() + word.slice(1); - } - }) - .join(" ") + "..." - ); + if (!str) { + return null; + } + + const formattedWords = str + .replace(/-/g, " ") + .split(" ") + .map((word, index) => { + word = word.toLowerCase(); + if (word === "ipfs" || word === "ipns") { + return word.toUpperCase(); + } else if (index === 0) { + return word.charAt(0).toUpperCase() + word.slice(1); + } else { + return word; + } + }); + + if (formattedWords.includes("Failed")) { + return formattedWords.join(" ") + "."; + } + + return formattedWords.join(" ") + "..."; } export default formatState; \ No newline at end of file diff --git a/src/utils/isValidUrl.js b/src/utils/isValidUrl.js new file mode 100644 index 00000000..c8cab128 --- /dev/null +++ b/src/utils/isValidUrl.js @@ -0,0 +1,10 @@ +const isValidUrl = (url) => { + try { + new URL(url); + return true; + } catch (e) { + return false; + } +}; + +export default isValidUrl; \ No newline at end of file