mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
+5
-1
@@ -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() {
|
||||
<Route path={`/profile/c/:index`} element={<Pending setBodyStyle={setBodyStyle} /> }>
|
||||
<Route path='settings' element={<Pending />} />
|
||||
</Route>
|
||||
<Route path={`/profile/p/subscriptions`} element={<Subscriptions setBodyStyle={setBodyStyle} /> }>
|
||||
<Route path={`/p/subscriptions`} element={<Subscriptions setBodyStyle={setBodyStyle} /> }>
|
||||
<Route path='settings' element={<Subscriptions />} />
|
||||
</Route>
|
||||
<Route path={`p/subscriptions/catalog`} element={<SubscriptionsCatalog setBodyStyle={setBodyStyle} /> }>
|
||||
<Route path='settings' element={<SubscriptionsCatalog />} />
|
||||
</Route>
|
||||
<Route path='*' element={<NotFound setBodyStyle={setBodyStyle} />} />
|
||||
</Routes>
|
||||
<Toast />
|
||||
|
||||
@@ -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 = () => {
|
||||
<>
|
||||
<span className="boardList">
|
||||
[
|
||||
<Link to={`/profile/p/subscriptions`}>Subscriptions</Link>
|
||||
<Link to={`/p/subscriptions`}>Subscriptions</Link>
|
||||
] [
|
||||
{defaultSubplebbits.map((subplebbit, index) => (
|
||||
<span className="boardList" key={`span-${subplebbit.address}`}>
|
||||
@@ -505,7 +516,7 @@ const Board = () => {
|
||||
</>
|
||||
) : (
|
||||
<div id="stats" style={{float: "right", marginTop: "5px"}}>
|
||||
<span>Fetching IPFS...</span>
|
||||
<span>{formatState(subplebbit.state)}</span>
|
||||
</div>
|
||||
)}
|
||||
<div id="catalog-button-mobile">
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<Helmet>
|
||||
@@ -262,7 +272,7 @@ const Catalog = () => {
|
||||
<>
|
||||
<span className="boardList">
|
||||
[
|
||||
<Link to={`/profile/p/subscriptions`}>Subscriptions</Link>
|
||||
<Link to={`/p/subscriptions`}>Subscriptions</Link>
|
||||
] [
|
||||
{defaultSubplebbits.map((subplebbit, index) => (
|
||||
<span className="boardList" key={`span-${subplebbit.address}`}>
|
||||
@@ -418,7 +428,7 @@ const Catalog = () => {
|
||||
</>
|
||||
) : (
|
||||
<div id="stats" style={{float: "right", marginTop: "5px"}}>
|
||||
<span>Fetching IPFS...</span>
|
||||
<span>{formatState(subplebbit.state)}</span>
|
||||
</div>
|
||||
)}
|
||||
<div id="return-button-mobile">
|
||||
@@ -426,9 +436,6 @@ const Catalog = () => {
|
||||
<Link to={`/p/${selectedAddress}`}>Return</Link>
|
||||
</span>
|
||||
</div>
|
||||
<div id="stats" style={{float: "right", marginTop: "5px"}}>
|
||||
{feed.length > 0 ? (null) : (<span>Fetching IPFS...</span>)}
|
||||
</div>
|
||||
<hr />
|
||||
</TopBar>
|
||||
<Threads selectedStyle={selectedStyle}>
|
||||
|
||||
@@ -92,13 +92,13 @@ const Home = () => {
|
||||
{account?.subscriptions?.length > 0 ? (
|
||||
<Boards>
|
||||
<BoardsTitle>
|
||||
<h2>Board Subscriptions</h2>
|
||||
<h2>Subscriptions</h2>
|
||||
</BoardsTitle>
|
||||
<BoardsContent id="subscriptions">
|
||||
<h3 style={{textDecoration: 'underline', display: 'inline'}}>
|
||||
You have subscribed to {account?.subscriptions?.length} board{account?.subscriptions?.length > 1 ? "s" : null}
|
||||
</h3>
|
||||
<Link to="/profile/p/subscriptions" id="view-all">[view all]</Link>
|
||||
<Link to="/p/subscriptions" id="view-all">[view all]</Link>
|
||||
<br />
|
||||
{account?.subscriptions?.map((subscription, index) => (
|
||||
<>
|
||||
|
||||
@@ -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 = () => {
|
||||
<Link to={`/p/${selectedAddress}/catalog`}>Catalog</Link>
|
||||
]
|
||||
</span>
|
||||
<span className="reply-stat">{formatState(comment.state)}</span>
|
||||
<span className="reply-stat">{formatState(publishingState)}</span>
|
||||
<hr />
|
||||
</TopBar>
|
||||
<Tooltip id="tooltip" className="tooltip" />
|
||||
|
||||
@@ -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 = () => {
|
||||
<>
|
||||
<span className="boardList">
|
||||
[
|
||||
<Link to={`/profile/p/subscriptions`}>Subscriptions</Link>
|
||||
<Link to={`/p/subscriptions`}>Subscriptions</Link>
|
||||
] [
|
||||
{defaultSubplebbits.map((subplebbit, index) => (
|
||||
<span className="boardList" key={`span-${subplebbit.address}`}>
|
||||
@@ -200,7 +190,7 @@ const Subscriptions = () => {
|
||||
}>Create Board</button>
|
||||
]
|
||||
[
|
||||
<Link to={`/profile/p/subscriptions/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
|
||||
<Link to={`/p/subscriptions/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
|
||||
]
|
||||
[
|
||||
<Link to="/" onClick={() => handleStyleChange({target: {value: "Yotsuba"}}
|
||||
@@ -225,7 +215,7 @@ const Subscriptions = () => {
|
||||
}>Create Board</button>
|
||||
</div>
|
||||
<div className="page-jump">
|
||||
<Link to={`/profile/p/subscriptions/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
|
||||
<Link to={`/p/subscriptions/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
|
||||
|
||||
<Link to="/" onClick={() => handleStyleChange({target: {value: "Yotsuba"}}
|
||||
)}>Home</Link>
|
||||
@@ -267,6 +257,16 @@ const Subscriptions = () => {
|
||||
<option value="Photon">Photon</option>
|
||||
</select>
|
||||
</span>
|
||||
<div id="catalog-button-desktop">
|
||||
[
|
||||
<Link to={`/p/subscriptions/catalog`}>Catalog</Link>
|
||||
]
|
||||
</div>
|
||||
<div id="catalog-button-mobile">
|
||||
<span className="btn-wrap">
|
||||
<Link to={`/p/subscriptions/catalog`}>Catalog</Link>
|
||||
</span>
|
||||
</div>
|
||||
</TopBar>
|
||||
<Tooltip id="tooltip" className="tooltip" />
|
||||
<BoardForm selectedStyle={selectedStyle}>
|
||||
@@ -1037,7 +1037,7 @@ const Subscriptions = () => {
|
||||
}>Create Board</button>
|
||||
]
|
||||
[
|
||||
<Link to={`/profile/p/subscriptions/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
|
||||
<Link to={`/p/subscriptions/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
|
||||
]
|
||||
[
|
||||
<Link to="/" onClick={() => handleStyleChange({target: {value: "Yotsuba"}}
|
||||
|
||||
@@ -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 (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>Subscriptions - Catalog - plebchan</title>
|
||||
</Helmet>
|
||||
<Container>
|
||||
<SettingsModal
|
||||
selectedStyle={selectedStyle}
|
||||
isOpen={isSettingsOpen}
|
||||
closeModal={() => setIsSettingsOpen(false)} />
|
||||
<NavBar selectedStyle={selectedStyle}>
|
||||
<>
|
||||
<span className="boardList">
|
||||
[
|
||||
<Link to={`/p/subscriptions`}>Subscriptions</Link>
|
||||
] [
|
||||
{defaultSubplebbits.map((subplebbit, index) => (
|
||||
<span className="boardList" key={`span-${subplebbit.address}`}>
|
||||
{index === 0 ? null : "\u00a0"}
|
||||
<Link to={`/p/${subplebbit.address}`} key={`a-${subplebbit.address}`} onClick={() => {
|
||||
setSelectedTitle(subplebbit.title);
|
||||
setSelectedAddress(subplebbit.address);
|
||||
}}
|
||||
>{subplebbit.title ? subplebbit.title : subplebbit.address}</Link>
|
||||
<OfflineIndicator
|
||||
address={subplebbit.address}
|
||||
className="offline-nav"
|
||||
tooltipPlace="bottom" />
|
||||
{index !== defaultSubplebbits.length - 1 ? " /" : null}
|
||||
</span>
|
||||
))}
|
||||
]
|
||||
</span>
|
||||
<span className="nav">
|
||||
[
|
||||
<button style={{all: 'unset', cursor: 'pointer'}} onClick={
|
||||
() => alert(
|
||||
'To create a board, first you have to run a full node.\nYou can run a full node by simply browsing with the plebbit desktop app. After you download it, open it, wait for it loading, then click on "Home" in the top left, then "Create Community".\n\nAfter you create the community, you can go back to plebchan at any time to see it as a board by pasting its address (begins with p/12D3KooW...) in the search bar, which is in the Home.\n\nNote:\n\n- Your community will be online for as long as you leave the app open, because it functions like a server for the community.\n- The longer you leave the app open, the more data you are seeding to the protocol, which helps performance for everybody.\n - All the data in the plebbit protocol is just text, which is extremely lightweight. All media is generated by links, which is text, embedded by the clients.\n\nDownload the plebbit app here: https://github.com/plebbit/plebbit-react/releases\n\nYou can also use a CLI: https://github.com/plebbit/plebbit-cli\n\nRunning boards in the plebchan app is a planned feature.\n\n'
|
||||
)
|
||||
}>Create Board</button>
|
||||
]
|
||||
[
|
||||
<Link to={`/p/${selectedAddress}/catalog/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
|
||||
]
|
||||
[
|
||||
<Link to="/" onClick={() => handleStyleChange({target: {value: "Yotsuba"}}
|
||||
)}>Home</Link>
|
||||
]
|
||||
</span>
|
||||
<div id="board-nav-mobile" style={{ top: visible ? 0 : '-23px' }}>
|
||||
<div className="board-select">
|
||||
<strong>Board</strong>
|
||||
|
||||
<select id="board-select-mobile" value={selectedAddress} onChange={handleSelectChange}>
|
||||
<option value="subscriptions">Subscriptions</option>
|
||||
{defaultSubplebbits.map(subplebbit => (
|
||||
<option key={`option-${subplebbit.address}`} value={subplebbit.address}
|
||||
>{subplebbit.title ? subplebbit.title : subplebbit.address}</option>
|
||||
))}
|
||||
</select>
|
||||
<button style={{all: 'unset', cursor: 'pointer'}} onClick={
|
||||
() => alert(
|
||||
'To create a board, first you have to run a full node.\nYou can run a full node by simply browsing with the plebbit desktop app. After you download it, open it, wait for it loading, then click on "Home" in the top left, then "Create Community".\n\nAfter you create the community, you can go back to plebchan at any time to see it as a board by pasting its address (begins with p/12D3KooW...) in the search bar, which is in the Home.\n\nNote:\n\n- Your community will be online for as long as you leave the app open, because it functions like a server for the community.\n- The longer you leave the app open, the more data you are seeding to the protocol, which helps performance for everybody.\n - All the data in the plebbit protocol is just text, which is extremely lightweight. All media is generated by links, which is text, embedded by the clients.\n\nDownload the plebbit app here: https://github.com/plebbit/plebbit-react/releases\n\nYou can also use a CLI: https://github.com/plebbit/plebbit-cli\n\nRunning boards in the plebchan app is a planned feature.\n\n'
|
||||
)
|
||||
}>Create Board</button>
|
||||
</div>
|
||||
<div className="page-jump">
|
||||
<Link to={`/p/${selectedAddress}/catalog/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
|
||||
|
||||
<Link to="/" onClick={() => handleStyleChange({target: {value: "Yotsuba"}}
|
||||
)}>Home</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div id="separator-mobile"> </div>
|
||||
<div id="separator-mobile"> </div>
|
||||
</>
|
||||
</NavBar>
|
||||
<Header selectedStyle={selectedStyle}>
|
||||
<>
|
||||
<div className="banner">
|
||||
<ImageBanner />
|
||||
</div>
|
||||
<>
|
||||
<div className="board-title">Subscriptions</div>
|
||||
{feed.length < 1 ? (
|
||||
<div className="board-address">You haven't subscribed to any board yet.</div>
|
||||
) : (
|
||||
<div className="board-address">
|
||||
You have subscribed to {account.subscriptions.length} board{account.subscriptions.length > 1 ? "s" : null}.
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
</>
|
||||
</Header>
|
||||
<Break selectedStyle={selectedStyle} />
|
||||
<TopBar selectedStyle={selectedStyle}>
|
||||
<hr />
|
||||
<span className="style-changer">
|
||||
Style:
|
||||
|
||||
<select id="style-selector" onChange={handleStyleChange} value={selectedStyle}>
|
||||
<option value="Yotsuba">Yotsuba</option>
|
||||
<option value="Yotsuba-B">Yotsuba B</option>
|
||||
<option value="Futaba">Futaba</option>
|
||||
<option value="Burichan">Burichan</option>
|
||||
<option value="Tomorrow">Tomorrow</option>
|
||||
<option value="Photon">Photon</option>
|
||||
</select>
|
||||
</span>
|
||||
<div className="return-button" id="return-button-desktop">
|
||||
[
|
||||
<Link to={`/p/subscriptions`}>Return</Link>
|
||||
]
|
||||
</div>
|
||||
<div id="return-button-mobile">
|
||||
<span className="btn-wrap-catalog btn-wrap">
|
||||
<Link to={`/p/subscriptions`}>Return</Link>
|
||||
</span>
|
||||
</div>
|
||||
<hr />
|
||||
</TopBar>
|
||||
<Threads selectedStyle={selectedStyle}>
|
||||
{selectedFeed.length < 1 ? (
|
||||
<CatalogLoader />
|
||||
) : (
|
||||
<InfiniteScroll
|
||||
pageStart={0}
|
||||
loadMore={tryLoadMore}
|
||||
hasMore={hasMore}
|
||||
>
|
||||
{feed.map((thread, index) => {
|
||||
const commentMediaInfo = getCommentMediaInfo(thread);
|
||||
const fallbackImgUrl = "assets/filedeleted-res.gif";
|
||||
return (
|
||||
<Link style={{all: "unset", cursor: "pointer"}} key={`link-${index}`} to={`/p/${selectedAddress}/c/${thread.cid}`} onClick={() => setSelectedThread(thread.cid)}>
|
||||
<div key={`thread-${index}`} className="thread">
|
||||
{commentMediaInfo?.url ? (
|
||||
<Fragment key="f-catalog">
|
||||
{commentMediaInfo?.type === "webpage" ? (
|
||||
thread.thumbnailUrl ? (
|
||||
<img key={`img-${index}`}
|
||||
src={commentMediaInfo.thumbnail} alt={commentMediaInfo.type}
|
||||
onError={(e) => {
|
||||
e.target.src = fallbackImgUrl
|
||||
e.target.onerror = null;
|
||||
}} />
|
||||
) : null
|
||||
) : null}
|
||||
{commentMediaInfo?.type === "image" ? (
|
||||
<img key={`img-${index}`}
|
||||
src={commentMediaInfo.url} alt={commentMediaInfo.type}
|
||||
onError={(e) => {
|
||||
e.target.src = fallbackImgUrl
|
||||
e.target.onerror = null;}} />
|
||||
) : null}
|
||||
{commentMediaInfo?.type === "video" ? (
|
||||
<video key={`fti-${index}`}
|
||||
src={commentMediaInfo.url}
|
||||
alt={commentMediaInfo.type}
|
||||
style={{ pointerEvents: "none" }}
|
||||
onError={(e) => e.target.src = fallbackImgUrl} />
|
||||
) : null}
|
||||
{commentMediaInfo?.type === "audio" ? (
|
||||
<audio controls
|
||||
key={`fti-${index}`}
|
||||
src={commentMediaInfo.url}
|
||||
alt={commentMediaInfo.type}
|
||||
style={{ pointerEvents: "none" }}
|
||||
onError={(e) => e.target.src = fallbackImgUrl} />
|
||||
) : null}
|
||||
</Fragment>
|
||||
) : null}
|
||||
{/* <div key={`ti-${index}`} className="thread-icons" >
|
||||
<span key={`si-${index}`} className="thread-icon sticky-icon" title="Sticky"></span>
|
||||
</div> */}
|
||||
<div key={`meta-${index}`} className="meta" title="(R)eplies / (I)mage Replies" >
|
||||
R:
|
||||
<b key={`b-${index}`}>{thread.replyCount}</b>
|
||||
</div>
|
||||
<div key={`t-${index}`} className="teaser">
|
||||
<b key={`b2-${index}`}>{thread.title ? `${thread.title}` : null}</b>
|
||||
{thread.content ? `: ${thread.content}` : null}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
)})}
|
||||
</InfiniteScroll>
|
||||
)}
|
||||
</Threads>
|
||||
<Footer selectedStyle={selectedStyle}>
|
||||
<Break id="break" selectedStyle={selectedStyle} style={{
|
||||
marginTop: "-36px",
|
||||
width: "100%",
|
||||
}} />
|
||||
<Break selectedStyle={selectedStyle} style={{
|
||||
width: "100%",
|
||||
}} />
|
||||
<span className="style-changer" style={{
|
||||
float: "right",
|
||||
marginTop: "2px",
|
||||
}}>
|
||||
Style:
|
||||
|
||||
<select id="style-selector" onChange={handleStyleChange} value={selectedStyle}>
|
||||
<option value="Yotsuba">Yotsuba</option>
|
||||
<option value="Yotsuba-B">Yotsuba B</option>
|
||||
<option value="Futaba">Futaba</option>
|
||||
<option value="Burichan">Burichan</option>
|
||||
<option value="Tomorrow">Tomorrow</option>
|
||||
<option value="Photon">Photon</option>
|
||||
</select>
|
||||
</span>
|
||||
<NavBar selectedStyle={selectedStyle} style={{
|
||||
marginTop: "42px",
|
||||
}}>
|
||||
<>
|
||||
<span className="boardList">
|
||||
[
|
||||
<Link to={`/p/subscriptions`}>Subscriptions</Link>
|
||||
] [
|
||||
{defaultSubplebbits.map((subplebbit, index) => (
|
||||
<span className="boardList" key={`span-${subplebbit.address}`}>
|
||||
{index === 0 ? null : "\u00a0"}
|
||||
<Link to={`/p/${subplebbit.address}`} key={`a-${subplebbit.address}`} onClick={() => handleClickTitle(subplebbit.title, subplebbit.address)}
|
||||
>{subplebbit.title ? subplebbit.title : subplebbit.address}</Link>
|
||||
<OfflineIndicator
|
||||
address={subplebbit.address}
|
||||
className="offline-nav"
|
||||
tooltipPlace="bottom" />
|
||||
{index !== defaultSubplebbits.length - 1 ? " /" : null}
|
||||
</span>
|
||||
))}
|
||||
]
|
||||
</span>
|
||||
<span className="nav">
|
||||
[
|
||||
<button style={{all: 'unset', cursor: 'pointer'}} onClick={
|
||||
() => alert(
|
||||
'To create a board, first you have to run a full node.\nYou can run a full node by simply browsing with the plebbit desktop app. After you download it, open it, wait for it loading, then click on "Home" in the top left, then "Create Community".\n\nAfter you create the community, you can go back to plebchan at any time to see it as a board by pasting its address (begins with p/12D3KooW...) in the search bar, which is in the Home.\n\nNote:\n\n- Your community will be online for as long as you leave the app open, because it functions like a server for the community.\n- The longer you leave the app open, the more data you are seeding to the protocol, which helps performance for everybody.\n - All the data in the plebbit protocol is just text, which is extremely lightweight. All media is generated by links, which is text, embedded by the clients.\n\nDownload the plebbit app here: https://github.com/plebbit/plebbit-react/releases\n\nYou can also use a CLI: https://github.com/plebbit/plebbit-cli\n\nRunning boards in the plebchan app is a planned feature.\n\n'
|
||||
)
|
||||
}>Create Board</button>
|
||||
]
|
||||
[
|
||||
<Link to={`/p/${selectedAddress}/catalog/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
|
||||
]
|
||||
[
|
||||
<Link to="/" onClick={() => handleStyleChange({target: {value: "Yotsuba"}}
|
||||
)}>Home</Link>
|
||||
]
|
||||
</span>
|
||||
</>
|
||||
</NavBar>
|
||||
<div id="version">
|
||||
plebchan v{version}. GPL-2.0
|
||||
</div>
|
||||
<div className="footer-links"
|
||||
style={{
|
||||
textAlign: "center",
|
||||
fontSize: "x-small",
|
||||
fontFamily: "arial",
|
||||
marginTop: "5px",
|
||||
marginBottom: "15px",
|
||||
}}>
|
||||
<a style={{textDecoration: 'underline'}} href="https://plebbit.com" target="_blank" rel="noopener noreferrer">About</a>
|
||||
•
|
||||
<a style={{textDecoration: 'underline'}} href="https://github.com/plebbit/plebchan/releases/latest" target="_blank" rel="noopener noreferrer">App</a>
|
||||
•
|
||||
<a style={{textDecoration: 'underline'}} href="https://twitter.com/plebchan_eth" target="_blank" rel="noopener noreferrer">Twitter</a>
|
||||
•
|
||||
<a style={{textDecoration: 'underline'}} href="https://t.me/plebbit" target="_blank" rel="noopener noreferrer">Telegram</a>
|
||||
</div>
|
||||
</Footer>
|
||||
</Container>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default SubscriptionsCatalog;
|
||||
@@ -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 = () => {
|
||||
<>
|
||||
<span className="boardList">
|
||||
[
|
||||
<Link to={`/profile/p/subscriptions`}>Subscriptions</Link>
|
||||
<Link to={`/p/subscriptions`}>Subscriptions</Link>
|
||||
] [
|
||||
{defaultSubplebbits.map((subplebbit, index) => (
|
||||
<span className="boardList" key={`span-${subplebbit.address}`}>
|
||||
|
||||
+23
-14
@@ -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;
|
||||
@@ -0,0 +1,10 @@
|
||||
const isValidUrl = (url) => {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export default isValidUrl;
|
||||
Reference in New Issue
Block a user