mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
+31
-1
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Route, Routes, useLocation } from 'react-router-dom';
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import { useAccount, useBufferedFeeds } from '@plebbit/plebbit-react-hooks';
|
||||
@@ -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';
|
||||
@@ -18,6 +20,8 @@ import Thread from './components/views/Thread';
|
||||
import CaptchaModal from './components/CaptchaModal';
|
||||
import { importAll } from './components/ImageBanner';
|
||||
import preloadImages from './utils/preloadImages';
|
||||
import useError from "./hooks/useError";
|
||||
import useSuccess from "./hooks/useSuccess";
|
||||
|
||||
|
||||
export default function App() {
|
||||
@@ -36,6 +40,26 @@ export default function App() {
|
||||
|
||||
const account = useAccount();
|
||||
|
||||
const [errorMessage, setErrorMessage] = useState(null);
|
||||
const [successMessage, setSuccessMessage] = useState(null);
|
||||
useError(errorMessage, [errorMessage]);
|
||||
useSuccess(successMessage, [successMessage]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const successToast = localStorage.getItem("successToast");
|
||||
const errorToast = localStorage.getItem("errorToast");
|
||||
if (successToast) {
|
||||
setSuccessMessage(successToast);
|
||||
localStorage.removeItem("successToast");
|
||||
} else if (errorToast) {
|
||||
setErrorMessage(errorToast);
|
||||
localStorage.removeItem("errorToast");
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}, [setErrorMessage, setSuccessMessage]);
|
||||
|
||||
// preload default subs and subscriptions
|
||||
useBufferedFeeds({
|
||||
feedsOptions: [
|
||||
@@ -169,6 +193,12 @@ export default function App() {
|
||||
<Route path={`p/subscriptions/catalog`} element={<SubscriptionsCatalog setBodyStyle={setBodyStyle} /> }>
|
||||
<Route path='settings' element={<SubscriptionsCatalog />} />
|
||||
</Route>
|
||||
<Route path={`p/all`} element={<All setBodyStyle={setBodyStyle} /> }>
|
||||
<Route path='settings' element={<All />} />
|
||||
</Route>
|
||||
<Route path={`p/all/catalog`} element={<AllCatalog setBodyStyle={setBodyStyle} /> }>
|
||||
<Route path='settings' element={<AllCatalog />} />
|
||||
</Route>
|
||||
<Route path='*' element={<NotFound setBodyStyle={setBodyStyle} />} />
|
||||
</Routes>
|
||||
<Toast />
|
||||
|
||||
@@ -6,8 +6,6 @@ const BoardAvatar = ({ address }) => {
|
||||
const [avatarUrl, setAvatarUrl] = useState("assets/plebchan.png");
|
||||
const subplebbit = useSubplebbit({ subplebbitAddress: address });
|
||||
|
||||
const isOnline = subplebbit.updatedAt > Date.now() / 1000 - 60 * 20;
|
||||
|
||||
useEffect(() => {
|
||||
if (subplebbit.suggested?.avatarUrl) {
|
||||
setAvatarUrl(subplebbit.suggested.avatarUrl);
|
||||
@@ -15,21 +13,11 @@ const BoardAvatar = ({ address }) => {
|
||||
}, [subplebbit.suggested?.avatarUrl, subplebbit]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="board-avatar-container">
|
||||
<img
|
||||
className="board-avatar"
|
||||
alt="board logo"
|
||||
src={avatarUrl}
|
||||
/>
|
||||
{!isOnline && (
|
||||
<img className="offline-indicator" alt="offline" src="assets/offline.png"
|
||||
data-tooltip-id="tooltip"
|
||||
data-tooltip-content="Offline"
|
||||
data-tooltip-place="top" />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
<img
|
||||
className="board-avatar"
|
||||
alt="board logo"
|
||||
src={avatarUrl}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@ const SettingsModal = ({ isOpen, closeModal }) => {
|
||||
|
||||
try {
|
||||
await setAccount({ ...account, plebbitOptions });
|
||||
localStorage.setItem("toastMessage", "Settings Saved");
|
||||
localStorage.setItem("successToast", "Settings Saved");
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
setErrorMessage(error.message);
|
||||
@@ -97,7 +97,7 @@ const SettingsModal = ({ isOpen, closeModal }) => {
|
||||
pubsubHttpClientsOptions: defaultPubsubHttpClientsOptions,
|
||||
},
|
||||
});
|
||||
localStorage.setItem("toastMessage", "Settings Reset");
|
||||
localStorage.setItem("successToast", "Settings Reset");
|
||||
window.location.reload();
|
||||
} catch (error) {
|
||||
setErrorMessage(error.message);
|
||||
@@ -158,9 +158,10 @@ const SettingsModal = ({ isOpen, closeModal }) => {
|
||||
const accountJson = importRef.current.value;
|
||||
|
||||
try {
|
||||
const parsedJson = JSON.parse(accountJson);
|
||||
await importAccount(accountJson);
|
||||
localStorage.setItem("toastMessage", "Account Imported");
|
||||
window.location.reload();
|
||||
setActiveAccount(parsedJson.account?.name);
|
||||
setSuccessMessage("Account Imported");
|
||||
|
||||
} catch (error) {
|
||||
setErrorMessage(error.message);
|
||||
|
||||
@@ -427,7 +427,7 @@ export const Header = styled.div`
|
||||
.offline {
|
||||
width: 16px;
|
||||
position: relative;
|
||||
margin: 2px 2px -4px 2px;
|
||||
margin: 2px 2px -2px 2px;
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -1402,6 +1402,31 @@ export const TopBar = styled.div`
|
||||
}
|
||||
}}
|
||||
|
||||
.ellipsis {
|
||||
margin-right: 14px;
|
||||
}
|
||||
|
||||
.ellipsis:after {
|
||||
content: "\\2026";
|
||||
position: absolute;
|
||||
-webkit-animation: ellipsis steps(4,end) 1200ms infinite;
|
||||
animation: ellipsis steps(4,end) 1500ms infinite;
|
||||
width: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@keyframes ellipsis {
|
||||
to {
|
||||
width: 1.25em;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes ellipsis {
|
||||
to {
|
||||
width: 1.25em;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 480px) {
|
||||
#catalog-button-mobile {
|
||||
display: none;
|
||||
@@ -1631,14 +1656,14 @@ export const BoardForm = styled.div`
|
||||
.offline-sub {
|
||||
width: 13px;
|
||||
margin-right: -5px;
|
||||
margin-bottom: -3px;
|
||||
margin-bottom: -1px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.offline-reply {
|
||||
width: 13px;
|
||||
margin-right: 2px;
|
||||
margin-bottom: -3px;
|
||||
margin-bottom: -1px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -2994,13 +3019,13 @@ export const BoardForm = styled.div`
|
||||
|
||||
.offline-mobile-sub {
|
||||
width: 13px;
|
||||
margin-top: 3px;
|
||||
margin-top: 4px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.offline-mobile-sub-reply {
|
||||
width: 13px;
|
||||
margin-top: 3px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.thread {
|
||||
|
||||
@@ -23,7 +23,7 @@ export const Threads = styled.div`
|
||||
max-height: 150px;
|
||||
}
|
||||
|
||||
img {
|
||||
.card {
|
||||
display: inline;
|
||||
margin: auto;
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, .25);
|
||||
@@ -45,7 +45,7 @@ export const Threads = styled.div`
|
||||
}
|
||||
|
||||
.offline-icon-no-link {
|
||||
height: 13px;
|
||||
height: 11px;
|
||||
position: absolute;
|
||||
margin-left: 13px;
|
||||
margin-top: -2px;
|
||||
|
||||
@@ -227,6 +227,31 @@ export const TopBar = styled.div`
|
||||
}
|
||||
}
|
||||
|
||||
.ellipsis {
|
||||
margin-right: 18px !important;
|
||||
}
|
||||
|
||||
.ellipsis:after {
|
||||
content: "\\2026";
|
||||
position: absolute;
|
||||
-webkit-animation: ellipsis steps(4,end) 1200ms infinite;
|
||||
animation: ellipsis steps(4,end) 1500ms infinite;
|
||||
width: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@keyframes ellipsis {
|
||||
to {
|
||||
width: 1.25em;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes ellipsis {
|
||||
to {
|
||||
width: 1.25em;
|
||||
}
|
||||
}
|
||||
|
||||
.return-button {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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 (
|
||||
<>
|
||||
<Helmet>
|
||||
<title>p/All - Catalog - plebchan</title>
|
||||
</Helmet>
|
||||
<Container>
|
||||
<SettingsModal
|
||||
selectedStyle={selectedStyle}
|
||||
isOpen={isSettingsOpen}
|
||||
closeModal={() => setIsSettingsOpen(false)} />
|
||||
<NavBar selectedStyle={selectedStyle}>
|
||||
<>
|
||||
<span className="boardList">
|
||||
[
|
||||
<Link to={`/p/all`}>All</Link>
|
||||
/
|
||||
<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>
|
||||
{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/all/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="all" onChange={handleSelectChange}>
|
||||
<option value="All">All</option>
|
||||
<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/all/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">p/All</div>
|
||||
<div className="board-address">Default boards, currently curated by devs.</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/all`}>Return</Link>
|
||||
]
|
||||
</div>
|
||||
<div id="return-button-mobile">
|
||||
<span className="btn-wrap-catalog btn-wrap">
|
||||
<Link to={`/p/all`}>Return</Link>
|
||||
</span>
|
||||
</div>
|
||||
{feed.length > 0 ? (
|
||||
null
|
||||
) : (
|
||||
<div id="stats" style={{float: "right", marginTop: "5px"}}>
|
||||
<span className={stateString ? "ellipsis" : ""}>{stateString}</span>
|
||||
</div>
|
||||
)}
|
||||
<hr />
|
||||
</TopBar>
|
||||
<Tooltip id="tooltip" className="tooltip" />
|
||||
<Threads selectedStyle={selectedStyle}>
|
||||
{ feed.length < 1 ? (
|
||||
null
|
||||
) : (
|
||||
<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/${thread.subplebbitAddress}/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 className="card" 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 className="card" key={`img-${index}`}
|
||||
src={commentMediaInfo.url} alt={commentMediaInfo.type}
|
||||
onError={(e) => {
|
||||
e.target.src = fallbackImgUrl
|
||||
e.target.onerror = null;}} />
|
||||
) : null}
|
||||
{commentMediaInfo?.type === "video" ? (
|
||||
<video className="card" key={`fti-${index}`}
|
||||
src={commentMediaInfo.url}
|
||||
alt={commentMediaInfo.type}
|
||||
style={{ pointerEvents: "none" }}
|
||||
onError={(e) => e.target.src = fallbackImgUrl} />
|
||||
) : null}
|
||||
{commentMediaInfo?.type === "audio" ? (
|
||||
<audio className="card" 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" >
|
||||
{(commentMediaInfo && (
|
||||
commentMediaInfo.type === 'image' ||
|
||||
commentMediaInfo.type === 'video' ||
|
||||
(commentMediaInfo.type === 'webpage' &&
|
||||
commentMediaInfo.thumbnail))) ? (
|
||||
// <span key={`si-${index}`} className="thread-icon sticky-icon" title="Sticky" /> */
|
||||
<OfflineIndicator
|
||||
address={thread.subplebbitAddress}
|
||||
className="thread-icon offline-icon"
|
||||
tooltipPlace="top" />
|
||||
) : (
|
||||
// <span key={`si-${index}`} className="thread-icon sticky-icon-no-link" title="Sticky" /> */
|
||||
<OfflineIndicator
|
||||
address={thread.subplebbitAddress}
|
||||
className="thread-icon offline-icon-no-link"
|
||||
tooltipPlace="top" />
|
||||
) }
|
||||
</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/all`}>All</Link>
|
||||
/
|
||||
<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>
|
||||
{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/all/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 AllCatalog;
|
||||
@@ -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 = () => {
|
||||
<>
|
||||
<span className="boardList">
|
||||
[
|
||||
<Link to={`/p/all`}>All</Link>
|
||||
/
|
||||
<Link to={`/p/subscriptions`}>Subscriptions</Link>
|
||||
] [
|
||||
{defaultSubplebbits.map((subplebbit, index) => (
|
||||
@@ -412,6 +417,7 @@ const Board = () => {
|
||||
<strong>Board</strong>
|
||||
|
||||
<select id="board-select-mobile" value={selectedAddress} onChange={handleSelectChange}>
|
||||
<option value="all">All</option>
|
||||
<option value="subscriptions">Subscriptions</option>
|
||||
{defaultSubplebbits.map(subplebbit => (
|
||||
<option key={`option-${subplebbit.address}`} value={subplebbit.address}
|
||||
@@ -530,7 +536,7 @@ const Board = () => {
|
||||
</>
|
||||
) : (
|
||||
<div id="stats" style={{float: "right", marginTop: "5px"}}>
|
||||
<span>{stateString}</span>
|
||||
<span className={stateString ? "ellipsis" : ""}>{stateString}</span>
|
||||
</div>
|
||||
)}
|
||||
<div id="catalog-button-mobile">
|
||||
@@ -1213,7 +1219,9 @@ const Board = () => {
|
||||
<>
|
||||
<span className="boardList">
|
||||
[
|
||||
<Link to="" onClick={() => {}}>Subscriptions</Link>
|
||||
<Link to={`/p/all`}>All</Link>
|
||||
/
|
||||
<Link to={`/p/subscriptions`}>Subscriptions</Link>
|
||||
]
|
||||
</span>
|
||||
{defaultSubplebbits.map((subplebbit, index) => (
|
||||
|
||||
@@ -253,6 +253,9 @@ const Catalog = () => {
|
||||
if (selected === 'subscriptions') {
|
||||
navigate(`/p/subscriptions`);
|
||||
return;
|
||||
} else if (selected === 'all') {
|
||||
navigate(`/p/all`);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedTitle = defaultSubplebbits.find((subplebbit) => subplebbit.address === selected).title;
|
||||
@@ -288,6 +291,8 @@ const Catalog = () => {
|
||||
<>
|
||||
<span className="boardList">
|
||||
[
|
||||
<Link to={`/p/all`}>All</Link>
|
||||
/
|
||||
<Link to={`/p/subscriptions`}>Subscriptions</Link>
|
||||
] [
|
||||
{defaultSubplebbits.map((subplebbit, index) => (
|
||||
@@ -324,6 +329,7 @@ const Catalog = () => {
|
||||
<strong>Board</strong>
|
||||
|
||||
<select id="board-select-mobile" value={selectedAddress} onChange={handleSelectChange}>
|
||||
<option value="all">All</option>
|
||||
<option value="subscriptions">Subscriptions</option>
|
||||
{defaultSubplebbits.map(subplebbit => (
|
||||
<option key={`option-${subplebbit.address}`} value={subplebbit.address}
|
||||
@@ -445,7 +451,7 @@ const Catalog = () => {
|
||||
</>
|
||||
) : (
|
||||
<div id="stats" style={{float: "right", marginTop: "5px"}}>
|
||||
<span>{stateString}</span>
|
||||
<span className={stateString ? "ellipsis" : ""}>{stateString}</span>
|
||||
</div>
|
||||
)}
|
||||
<div id="return-button-mobile">
|
||||
@@ -471,13 +477,13 @@ const Catalog = () => {
|
||||
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); window.scrollTo(0, 0);}}>
|
||||
<div key={`thread-${index}`} className="thread">
|
||||
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}`}
|
||||
<img className="card" key={`img-${index}`}
|
||||
src={commentMediaInfo.thumbnail} alt={commentMediaInfo.type}
|
||||
onError={(e) => {
|
||||
e.target.src = fallbackImgUrl
|
||||
@@ -486,21 +492,21 @@ const Catalog = () => {
|
||||
) : null
|
||||
) : null}
|
||||
{commentMediaInfo?.type === "image" ? (
|
||||
<img key={`img-${index}`}
|
||||
<img className="card" 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}`}
|
||||
<video className="card" key={`fti-${index}`}
|
||||
src={commentMediaInfo.url}
|
||||
alt={commentMediaInfo.type}
|
||||
style={{ pointerEvents: "none" }}
|
||||
onError={(e) => e.target.src = fallbackImgUrl} />
|
||||
) : null}
|
||||
{commentMediaInfo?.type === "audio" ? (
|
||||
<audio controls
|
||||
<audio className="card" controls
|
||||
key={`fti-${index}`}
|
||||
src={commentMediaInfo.url}
|
||||
alt={commentMediaInfo.type}
|
||||
@@ -556,7 +562,9 @@ const Catalog = () => {
|
||||
<>
|
||||
<span className="boardList">
|
||||
[
|
||||
<Link to="" onClick={() => {}}>Subscriptions</Link>
|
||||
<Link to={`/p/all`}>All</Link>
|
||||
/
|
||||
<Link to={`/p/subscriptions`}>Subscriptions</Link>
|
||||
]
|
||||
</span>
|
||||
{defaultSubplebbits.map((subplebbit, index) => (
|
||||
|
||||
@@ -107,11 +107,11 @@ const Home = () => {
|
||||
to={`/p/${subscription}`}>
|
||||
<br id="mobile-br" />
|
||||
{subscription}
|
||||
<OfflineIndicator
|
||||
</Link>
|
||||
<OfflineIndicator
|
||||
address={subscription}
|
||||
className="disconnected"
|
||||
tooltipPlace="top" />
|
||||
</Link>
|
||||
<br />
|
||||
</>
|
||||
))}
|
||||
@@ -129,13 +129,19 @@ const Home = () => {
|
||||
<div className="board-title">
|
||||
{subplebbit.title ? subplebbit.title : <span style={{userSelect: "none"}}> </span>}
|
||||
</div>
|
||||
<Link to={`/p/${subplebbit.address}`} onClick={() => {
|
||||
setSelectedTitle(subplebbit.title);
|
||||
setSelectedAddress(subplebbit.address);
|
||||
window.scrollTo(0, 0);
|
||||
}} >
|
||||
<BoardAvatar address={subplebbit.address} />
|
||||
</Link>
|
||||
<div className="board-avatar-container">
|
||||
<Link to={`/p/${subplebbit.address}`} onClick={() => {
|
||||
setSelectedTitle(subplebbit.title);
|
||||
setSelectedAddress(subplebbit.address);
|
||||
window.scrollTo(0, 0);
|
||||
}} >
|
||||
<BoardAvatar address={subplebbit.address} />
|
||||
</Link>
|
||||
<OfflineIndicator
|
||||
address={subplebbit.address}
|
||||
className="offline-indicator"
|
||||
tooltipPlace="top" />
|
||||
</div>
|
||||
<div className="board-text">
|
||||
<b>{subplebbit.address}</b>
|
||||
</div>
|
||||
|
||||
@@ -69,6 +69,15 @@ const Pending = () => {
|
||||
// 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);
|
||||
@@ -98,6 +107,8 @@ const Pending = () => {
|
||||
<>
|
||||
<span className="boardList">
|
||||
[
|
||||
<Link to={`/p/all`}>All</Link>
|
||||
/
|
||||
<Link to={`/p/subscriptions`}>Subscriptions</Link>
|
||||
] [
|
||||
{defaultSubplebbits.map((subplebbit, index) => (
|
||||
@@ -127,6 +138,8 @@ const Pending = () => {
|
||||
<strong>Board</strong>
|
||||
|
||||
<select id="board-select-mobile" value={selectedAddress} onChange={handleSelectChange}>
|
||||
<option value="all">All</option>
|
||||
<option value="subscriptions">Subscriptions</option>
|
||||
{defaultSubplebbits.map(subplebbit => (
|
||||
<option key={`option-${subplebbit.address}`} value={subplebbit.address}
|
||||
>{subplebbit.title ? subplebbit.title : subplebbit.address}</option>
|
||||
@@ -195,7 +208,7 @@ const Pending = () => {
|
||||
<Link to={`/p/${selectedAddress}/catalog`} onClick={()=> {window.scrollTo(0, 0)}}>Catalog</Link>
|
||||
]
|
||||
</span>
|
||||
<span className="reply-stat">{stateString}</span>
|
||||
<span className={stateString ? "reply-stat ellipsis" : ""}>{stateString}</span>
|
||||
<hr />
|
||||
</TopBar>
|
||||
<Tooltip id="tooltip" className="tooltip" />
|
||||
|
||||
@@ -165,6 +165,15 @@ const Subscriptions = () => {
|
||||
// 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);
|
||||
@@ -190,6 +199,8 @@ const Subscriptions = () => {
|
||||
<>
|
||||
<span className="boardList">
|
||||
[
|
||||
<Link to={`/p/all`}>All</Link>
|
||||
/
|
||||
<Link to={`/p/subscriptions`}>Subscriptions</Link>
|
||||
] [
|
||||
{defaultSubplebbits.map((subplebbit, index) => (
|
||||
@@ -223,7 +234,8 @@ const Subscriptions = () => {
|
||||
<strong>Board</strong>
|
||||
|
||||
<select id="board-select-mobile" value="subscriptions" onChange={handleSelectChange}>
|
||||
<option value="">Subscriptions</option>
|
||||
<option value="all">All</option>
|
||||
<option value="subscriptions">Subscriptions</option>
|
||||
{defaultSubplebbits.map(subplebbit => (
|
||||
<option key={`option-${subplebbit.address}`} value={subplebbit.address}
|
||||
>{subplebbit.title ? subplebbit.title : subplebbit.address}</option>
|
||||
@@ -287,7 +299,7 @@ const Subscriptions = () => {
|
||||
null
|
||||
) : (
|
||||
<div id="stats" style={{float: "right", marginTop: "5px"}}>
|
||||
<span>{stateString}</span>
|
||||
<span className={stateString ? "ellipsis" : ""}>{stateString}</span>
|
||||
</div>
|
||||
)}
|
||||
<div id="catalog-button-mobile">
|
||||
@@ -300,7 +312,6 @@ const Subscriptions = () => {
|
||||
<BoardForm selectedStyle={selectedStyle}>
|
||||
<div className="board">
|
||||
{ feed.length < 1 ? (
|
||||
// <PostLoader />
|
||||
null
|
||||
) : (
|
||||
<Virtuoso
|
||||
@@ -417,6 +428,7 @@ const Subscriptions = () => {
|
||||
setIsReplyOpen(true);
|
||||
setSelectedShortCid(thread.shortCid);
|
||||
setSelectedParentCid(thread.cid);
|
||||
setSelectedAddress(thread.subplebbitAddress);
|
||||
}} title="Reply to this post">{thread.shortCid}
|
||||
</Link>
|
||||
p/
|
||||
@@ -427,7 +439,7 @@ const Subscriptions = () => {
|
||||
data-tooltip-content={thread.subplebbitAddress}
|
||||
data-tooltip-place="top"
|
||||
>{thread.subplebbitAddress.slice(0, 10) + "(...)"}</span>
|
||||
)}
|
||||
)}
|
||||
</Link>
|
||||
<OfflineIndicator key={`oi-${index}`}
|
||||
address={thread.subplebbitAddress}
|
||||
@@ -545,6 +557,7 @@ const Subscriptions = () => {
|
||||
setIsReplyOpen(true);
|
||||
setSelectedShortCid(reply.shortCid);
|
||||
setSelectedParentCid(reply.cid);
|
||||
setSelectedAddress(thread.subplebbitAddress);
|
||||
}} title="Reply to this post">{reply.shortCid}</Link>
|
||||
) : (
|
||||
<span key="pending" style={{color: 'red', fontWeight: '700'}}>Pending</span>
|
||||
@@ -552,28 +565,20 @@ const Subscriptions = () => {
|
||||
p/
|
||||
<Link key={`p-t-${index}`} to={`/p/${thread.subplebbitAddress}`} id="reply-button" title="Visit this board">
|
||||
{thread.subplebbitAddress.includes(".eth") ?
|
||||
(thread.subplebbitAddress) :
|
||||
(
|
||||
<>
|
||||
{thread.subplebbitAddress}
|
||||
<OfflineIndicator key={`offline-reply-${index}`}
|
||||
address={thread.subplebbitAddress}
|
||||
tooltipPlace="top"
|
||||
className="offline-reply" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span key={`short-add${index}`}
|
||||
<span key={`short-add${index}`}
|
||||
data-tooltip-id="tooltip"
|
||||
data-tooltip-content={thread.subplebbitAddress}
|
||||
data-tooltip-place="top"
|
||||
>{thread.subplebbitAddress.slice(0, 10) + "(...)"}</span>
|
||||
<OfflineIndicator key={`offline-reply-${index}`}
|
||||
address={thread.subplebbitAddress}
|
||||
tooltipPlace="top"
|
||||
className="offline-reply" />
|
||||
</>
|
||||
)}
|
||||
>{thread.subplebbitAddress.slice(0, 10) + "(...)"}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
<OfflineIndicator key={`offline-reply-${index}`}
|
||||
address={thread.subplebbitAddress}
|
||||
tooltipPlace="top"
|
||||
className="offline-reply" />
|
||||
</span>
|
||||
<button key={`pmb-${index}`} className="post-menu-button" title="Post menu" style={{ all: 'unset', cursor: 'pointer' }} data-cmd="post-menu">▶</button>
|
||||
<div id="backlink-id" className="backlink">
|
||||
@@ -728,28 +733,20 @@ const Subscriptions = () => {
|
||||
p/
|
||||
<Link key={`p-t-${index}`} to={`/p/${thread.subplebbitAddress}`} id="reply-button" title="Visit this board">
|
||||
{thread.subplebbitAddress.includes(".eth") ?
|
||||
(thread.subplebbitAddress) :
|
||||
(
|
||||
<>
|
||||
{thread.subplebbitAddress}
|
||||
<OfflineIndicator key={`offline-indicator-${index}`}
|
||||
address={thread.subplebbitAddress}
|
||||
tooltipPlace="top"
|
||||
className="offline-mobile-sub" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span key={`short-add${index}`}
|
||||
<span key={`short-add${index}`}
|
||||
data-tooltip-id="tooltip"
|
||||
data-tooltip-content={thread.subplebbitAddress}
|
||||
data-tooltip-place="top"
|
||||
>{thread.subplebbitAddress.slice(0, 10) + "(...)"}</span>
|
||||
<OfflineIndicator key={`offline-indicator-${index}`}
|
||||
address={thread.subplebbitAddress}
|
||||
tooltipPlace="top"
|
||||
className="offline-mobile-sub" />
|
||||
</>
|
||||
)}
|
||||
>{thread.subplebbitAddress.slice(0, 10) + "(...)"}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
<OfflineIndicator key={`offline-indicator-${index}`}
|
||||
address={thread.subplebbitAddress}
|
||||
tooltipPlace="top"
|
||||
className="offline-mobile-sub" />
|
||||
</span>
|
||||
<span key={`mob-dt-${index}`} className="date-time-mobile post-number-mobile">
|
||||
{getDate(thread.timestamp)}
|
||||
@@ -762,6 +759,7 @@ const Subscriptions = () => {
|
||||
setIsReplyOpen(true);
|
||||
setSelectedShortCid(thread.shortCid);
|
||||
setSelectedParentCid(thread.cid);
|
||||
setSelectedAddress(thread.subplebbitAddress);
|
||||
}} title="Reply to this post">{thread.shortCid}
|
||||
</Link>
|
||||
</span>
|
||||
@@ -892,30 +890,22 @@ const Subscriptions = () => {
|
||||
</span>
|
||||
<span key={`p-t-mob-span-${index}`} className="date-time-mobile highlight-address-mobile">
|
||||
p/
|
||||
<Link key={`p-t-${index}`} to={`/p/${thread.subplebbitAddress}`} id="reply-button" title="Visit this board">
|
||||
<Link key={`p-t-${index}`} to={`/p/${thread.subplebbitAddress}`} id="reply-button" title="Visit this board">
|
||||
{thread.subplebbitAddress.includes(".eth") ?
|
||||
(thread.subplebbitAddress) :
|
||||
(
|
||||
<>
|
||||
{thread.subplebbitAddress}
|
||||
<OfflineIndicator key={`offline-indicator-${index}`}
|
||||
address={thread.subplebbitAddress}
|
||||
tooltipPlace="top"
|
||||
className="offline-mobile-sub-reply" />
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<span key={`short-add${index}`}
|
||||
<span key={`short-add${index}`}
|
||||
data-tooltip-id="tooltip"
|
||||
data-tooltip-content={thread.subplebbitAddress}
|
||||
data-tooltip-place="top"
|
||||
>{thread.subplebbitAddress.slice(0, 10) + "(...)"}</span>
|
||||
<OfflineIndicator key={`offline-indicator-${index}`}
|
||||
address={thread.subplebbitAddress}
|
||||
tooltipPlace="top"
|
||||
className="offline-mobile-sub-reply" />
|
||||
</>
|
||||
)}
|
||||
>{thread.subplebbitAddress.slice(0, 10) + "(...)"}
|
||||
</span>
|
||||
)}
|
||||
</Link>
|
||||
<OfflineIndicator key={`offline-indicator-${index}`}
|
||||
address={thread.subplebbitAddress}
|
||||
tooltipPlace="top"
|
||||
className="offline-mobile-sub-reply" />
|
||||
</span>
|
||||
<span key={`mob-dt-${index}`} className="date-time-mobile post-number-mobile">
|
||||
{getDate(reply.timestamp)}
|
||||
@@ -928,6 +918,7 @@ const Subscriptions = () => {
|
||||
setIsReplyOpen(true);
|
||||
setSelectedShortCid(reply.shortCid);
|
||||
setSelectedParentCid(reply.cid);
|
||||
setSelectedAddress(thread.subplebbitAddress);
|
||||
}} title="Reply to this post">{reply.shortCid}
|
||||
</Link>
|
||||
) : (
|
||||
|
||||
@@ -3,19 +3,19 @@ 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 } from '@plebbit/plebbit-react-hooks';
|
||||
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 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 useFeedStateString from '../../hooks/useFeedStateString';
|
||||
import packageJson from '../../../package.json'
|
||||
const {version} = packageJson
|
||||
|
||||
@@ -36,7 +36,10 @@ const SubscriptionsCatalog = () => {
|
||||
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 [setSelectedFeed] = useState(feed.sort((a, b) => b.timestamp - a.timestamp));
|
||||
const {subplebbits} = useSubplebbits({subplebbitAddresses: account?.subscriptions, sortType: 'new'});
|
||||
|
||||
const stateString = useFeedStateString(subplebbits);
|
||||
|
||||
const [errorMessage] = useState(null);
|
||||
useError(errorMessage, [errorMessage]);
|
||||
@@ -73,6 +76,10 @@ const SubscriptionsCatalog = () => {
|
||||
const selected = event.target.value;
|
||||
|
||||
if (selected === 'subscriptions') {
|
||||
navigate(`/p/subscriptions`);
|
||||
return;
|
||||
} else if (selected === 'all') {
|
||||
navigate(`/p/all`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -97,6 +104,8 @@ const SubscriptionsCatalog = () => {
|
||||
<>
|
||||
<span className="boardList">
|
||||
[
|
||||
<Link to={`/p/all`}>All</Link>
|
||||
/
|
||||
<Link to={`/p/subscriptions`}>Subscriptions</Link>
|
||||
] [
|
||||
{defaultSubplebbits.map((subplebbit, index) => (
|
||||
@@ -121,7 +130,7 @@ const SubscriptionsCatalog = () => {
|
||||
}>Create Board</button>
|
||||
]
|
||||
[
|
||||
<Link to={`/p/${selectedAddress}/catalog/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
|
||||
<Link to={`/p/subscriptions/catalog/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
|
||||
]
|
||||
[
|
||||
<Link to="/" onClick={() => handleStyleChange({target: {value: "Yotsuba"}}
|
||||
@@ -132,7 +141,8 @@ const SubscriptionsCatalog = () => {
|
||||
<div className="board-select">
|
||||
<strong>Board</strong>
|
||||
|
||||
<select id="board-select-mobile" value={selectedAddress} onChange={handleSelectChange}>
|
||||
<select id="board-select-mobile" value="subscriptions" onChange={handleSelectChange}>
|
||||
<option value="all">All</option>
|
||||
<option value="subscriptions">Subscriptions</option>
|
||||
{defaultSubplebbits.map(subplebbit => (
|
||||
<option key={`option-${subplebbit.address}`} value={subplebbit.address}
|
||||
@@ -146,7 +156,7 @@ const SubscriptionsCatalog = () => {
|
||||
}>Create Board</button>
|
||||
</div>
|
||||
<div className="page-jump">
|
||||
<Link to={`/p/${selectedAddress}/catalog/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
|
||||
<Link to={`/p/subscriptions/catalog/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
|
||||
|
||||
<Link to="/" onClick={() => handleStyleChange({target: {value: "Yotsuba"}}
|
||||
)}>Home</Link>
|
||||
@@ -198,12 +208,19 @@ const SubscriptionsCatalog = () => {
|
||||
<Link to={`/p/subscriptions`}>Return</Link>
|
||||
</span>
|
||||
</div>
|
||||
{feed.length > 0 ? (
|
||||
null
|
||||
) : (
|
||||
<div id="stats" style={{float: "right", marginTop: "5px"}}>
|
||||
<span className={stateString ? "ellipsis" : ""}>{stateString}</span>
|
||||
</div>
|
||||
)}
|
||||
<hr />
|
||||
</TopBar>
|
||||
<Tooltip id="tooltip" className="tooltip" />
|
||||
<Threads selectedStyle={selectedStyle}>
|
||||
{selectedFeed.length < 1 ? (
|
||||
<CatalogLoader />
|
||||
{ feed.length < 1 ? (
|
||||
null
|
||||
) : (
|
||||
<InfiniteScroll
|
||||
pageStart={0}
|
||||
@@ -214,14 +231,14 @@ const SubscriptionsCatalog = () => {
|
||||
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); window.scrollTo(0, 0);}}>
|
||||
<Link style={{all: "unset", cursor: "pointer"}} key={`link-${index}`} to={`/p/${thread.subplebbitAddress}/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}`}
|
||||
<img className="card" key={`img-${index}`}
|
||||
src={commentMediaInfo.thumbnail} alt={commentMediaInfo.type}
|
||||
onError={(e) => {
|
||||
e.target.src = fallbackImgUrl
|
||||
@@ -230,21 +247,21 @@ const SubscriptionsCatalog = () => {
|
||||
) : null
|
||||
) : null}
|
||||
{commentMediaInfo?.type === "image" ? (
|
||||
<img key={`img-${index}`}
|
||||
<img className="card" 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}`}
|
||||
<video className="card" key={`fti-${index}`}
|
||||
src={commentMediaInfo.url}
|
||||
alt={commentMediaInfo.type}
|
||||
style={{ pointerEvents: "none" }}
|
||||
onError={(e) => e.target.src = fallbackImgUrl} />
|
||||
) : null}
|
||||
{commentMediaInfo?.type === "audio" ? (
|
||||
<audio controls
|
||||
<audio className="card" controls
|
||||
key={`fti-${index}`}
|
||||
src={commentMediaInfo.url}
|
||||
alt={commentMediaInfo.type}
|
||||
@@ -254,14 +271,18 @@ const SubscriptionsCatalog = () => {
|
||||
</Fragment>
|
||||
) : null}
|
||||
<div key={`ti-${index}`} className="thread-icons" >
|
||||
{thread.link ? (
|
||||
// <span key={`si-${index}`} className="thread-icon sticky-icon" title="Sticky"></span> */
|
||||
{(commentMediaInfo && (
|
||||
commentMediaInfo.type === 'image' ||
|
||||
commentMediaInfo.type === 'video' ||
|
||||
(commentMediaInfo.type === 'webpage' &&
|
||||
commentMediaInfo.thumbnail))) ? (
|
||||
// <span key={`si-${index}`} className="thread-icon sticky-icon" title="Sticky" /> */
|
||||
<OfflineIndicator
|
||||
address={thread.subplebbitAddress}
|
||||
className="thread-icon offline-icon"
|
||||
tooltipPlace="top" />
|
||||
) : (
|
||||
// <span key={`si-${index}`} className="thread-icon sticky-icon-no-link" title="Sticky"></span> */
|
||||
// <span key={`si-${index}`} className="thread-icon sticky-icon-no-link" title="Sticky" /> */
|
||||
<OfflineIndicator
|
||||
address={thread.subplebbitAddress}
|
||||
className="thread-icon offline-icon-no-link"
|
||||
@@ -318,10 +339,6 @@ const SubscriptionsCatalog = () => {
|
||||
{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>
|
||||
))}
|
||||
@@ -336,7 +353,7 @@ const SubscriptionsCatalog = () => {
|
||||
}>Create Board</button>
|
||||
]
|
||||
[
|
||||
<Link to={`/p/${selectedAddress}/catalog/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
|
||||
<Link to={`/p/subscriptions/catalog/settings`} onClick={() => setIsSettingsOpen(true)}>Settings</Link>
|
||||
]
|
||||
[
|
||||
<Link to="/" onClick={() => handleStyleChange({target: {value: "Yotsuba"}}
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Helmet } from 'react-helmet-async';
|
||||
import { Link, useNavigate, useParams } from 'react-router-dom';
|
||||
import { Tooltip } from 'react-tooltip';
|
||||
import { useAccount, useAccountComments, useComment, usePublishComment } from '@plebbit/plebbit-react-hooks';
|
||||
import { useAccount, useAccountComments, useComment, usePublishComment, useSubplebbit } 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';
|
||||
@@ -38,7 +38,7 @@ const Thread = () => {
|
||||
setResolveCaptchaPromise,
|
||||
setPendingComment,
|
||||
setPendingCommentIndex,
|
||||
selectedAddress, setSelectedAddress,
|
||||
setSelectedAddress,
|
||||
setSelectedParentCid,
|
||||
setSelectedShortCid,
|
||||
selectedStyle,
|
||||
@@ -65,12 +65,26 @@ const Thread = () => {
|
||||
const comment = useComment({commentCid: selectedThread});
|
||||
const { subplebbitAddress, threadCid } = useParams();
|
||||
const handleClickForm = useClickForm();
|
||||
const subplebbit = useSubplebbit({subplebbitAddress: comment.subplebbitAddress});
|
||||
const selectedAddress = subplebbit.address;
|
||||
|
||||
const stateString = useStateString(comment);
|
||||
|
||||
const commentMediaInfo = getCommentMediaInfo(comment);
|
||||
const fallbackImgUrl = "assets/filedeleted-res.gif";
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
window.scrollTo(0, 0);
|
||||
}, []);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if ( comment.state === "failed" && selectedAddress === undefined) {
|
||||
navigate('/404');
|
||||
}
|
||||
}, [selectedAddress, navigate, comment.state]);
|
||||
|
||||
|
||||
const errorString = useMemo(() => {
|
||||
if (comment?.state === 'failed') {
|
||||
@@ -285,6 +299,9 @@ const Thread = () => {
|
||||
if (selected === 'subscriptions') {
|
||||
navigate(`/p/subscriptions`);
|
||||
return;
|
||||
} else if (selected === 'all') {
|
||||
navigate(`/p/all`);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedTitle = defaultSubplebbits.find((subplebbit) => subplebbit.address === selected).title;
|
||||
@@ -318,6 +335,8 @@ const Thread = () => {
|
||||
<>
|
||||
<span className="boardList">
|
||||
[
|
||||
<Link to={`/p/all`}>All</Link>
|
||||
/
|
||||
<Link to={`/p/subscriptions`}>Subscriptions</Link>
|
||||
] [
|
||||
{defaultSubplebbits.map((subplebbit, index) => (
|
||||
@@ -354,6 +373,7 @@ const Thread = () => {
|
||||
<strong>Board</strong>
|
||||
|
||||
<select id="board-select-mobile" value={selectedAddress} onChange={handleSelectChange}>
|
||||
<option value="all">All</option>
|
||||
<option value="subscriptions">Subscriptions</option>
|
||||
{defaultSubplebbits.map(subplebbit => (
|
||||
<option key={`option-${subplebbit.address}`} value={subplebbit.address}
|
||||
@@ -493,10 +513,10 @@ const Thread = () => {
|
||||
<span className="reply-stat">No replies yet</span>
|
||||
)
|
||||
) : (
|
||||
<span className="reply-stat">{stateString}</span>
|
||||
<span className={stateString ? "reply-stat ellipsis" : ""}>{stateString}</span>
|
||||
)
|
||||
) : (
|
||||
<span className="reply-stat">{stateString}</span>
|
||||
<span className={stateString ? "reply-stat ellipsis" : ""}>{stateString}</span>
|
||||
)}
|
||||
<hr />
|
||||
</TopBar>
|
||||
@@ -1066,10 +1086,10 @@ const Thread = () => {
|
||||
<span className="reply-stat">No replies yet</span>
|
||||
)
|
||||
) : (
|
||||
<span className="reply-stat">{stateString}</span>
|
||||
<span className={stateString ? "reply-stat ellipsis" : ""}>{stateString}</span>
|
||||
)
|
||||
) : (
|
||||
<span className="reply-stat">{stateString}</span>
|
||||
<span className={stateString ? "reply-stat ellipsis" : ""}>{stateString}</span>
|
||||
)}
|
||||
<hr />
|
||||
</div>
|
||||
@@ -1171,7 +1191,9 @@ const Thread = () => {
|
||||
<>
|
||||
<span className="boardList">
|
||||
[
|
||||
<Link to="" onClick={() => {}}>Subscriptions</Link>
|
||||
<Link to={`/p/all`}>All</Link>
|
||||
/
|
||||
<Link to={`/p/subscriptions`}>Subscriptions</Link>
|
||||
]
|
||||
</span>
|
||||
{defaultSubplebbits.map((subplebbit, index) => (
|
||||
|
||||
Reference in New Issue
Block a user