diff --git a/src/App.js b/src/App.js
index 40a8b936..c9b59773 100755
--- a/src/App.js
+++ b/src/App.js
@@ -28,8 +28,18 @@ import packageJson from '../package.json';
const commitRef = process?.env?.REACT_APP_COMMIT_REF || '';
export default function App() {
- const { bodyStyle, setBodyStyle, setDefaultSubplebbits, isCaptchaOpen, setIsCaptchaOpen, setIsSettingsOpen, selectedStyle, setShowPostForm, setShowPostFormLink } =
- useGeneralStore((state) => state);
+ const {
+ bodyStyle,
+ setBodyStyle,
+ setDefaultSubplebbits,
+ setDefaultNsfwSubplebbits,
+ isCaptchaOpen,
+ setIsCaptchaOpen,
+ setIsSettingsOpen,
+ selectedStyle,
+ setShowPostForm,
+ setShowPostFormLink,
+ } = useGeneralStore((state) => state);
const location = useLocation();
const isElectron = window.electron && window.electron.isElectron;
@@ -116,6 +126,21 @@ export default function App() {
};
}, [setDefaultSubplebbits]);
+ // fetch default NSFW subplebbits
+ useEffect(() => {
+ let didCancel = false;
+ fetch('https://raw.githubusercontent.com/plebbit/temporary-default-subplebbits/master/subplebbits-nsfw.json', { cache: 'no-cache' })
+ .then((res) => res.json())
+ .then((res) => {
+ if (!didCancel) {
+ setDefaultNsfwSubplebbits(res);
+ }
+ });
+ return () => {
+ didCancel = true;
+ };
+ }, [setDefaultNsfwSubplebbits]);
+
// handle nested routes
useEffect(() => {
const path = location.pathname;
diff --git a/src/components/styled/views/Home.styled.jsx b/src/components/styled/views/Home.styled.jsx
index 8c263c07..6903f681 100644
--- a/src/components/styled/views/Home.styled.jsx
+++ b/src/components/styled/views/Home.styled.jsx
@@ -44,7 +44,7 @@ export const Search = styled.div`
text-align: center;
margin-bottom: 20px;
- input[type="text"] {
+ input[type='text'] {
width: 400px;
height: 30px;
font-size: 18px;
@@ -60,27 +60,27 @@ export const Search = styled.div`
font-family: arial, helvetica, sans-serif;
}
- input[type="submit"] {
- width: 60px;
+ input[type='submit'] {
+ width: 70px;
height: 30px;
font-size: 14px;
margin-left: 5px;
}
-
+
@-moz-document url-prefix() {
- input[type="submit"] {
+ input[type='submit'] {
vertical-align: top;
}
}
- @media screen and (-webkit-min-device-pixel-ratio:0) {
- input[type="submit"] {
+ @media screen and (-webkit-min-device-pixel-ratio: 0) {
+ input[type='submit'] {
vertical-align: center;
}
}
- @media not all and (min-resolution:.001dpcm) {
- input[type="submit"] {
+ @media not all and (min-resolution: 0.001dpcm) {
+ input[type='submit'] {
vertical-align: top;
}
}
@@ -88,7 +88,7 @@ export const Search = styled.div`
@media only screen and (max-width: 480px) {
text-align: center;
- input[type="text"] {
+ input[type='text'] {
width: 80%;
height: 35px;
font-size: 20px;
@@ -96,8 +96,8 @@ export const Search = styled.div`
box-sizing: border-box;
border-radius: 0;
}
-
- input[type="submit"] {
+
+ input[type='submit'] {
height: 35px;
font-size: 14px;
padding: 5px;
@@ -107,7 +107,6 @@ export const Search = styled.div`
-webkit-appearance: none;
}
}
-
`;
export const Page = styled.div`
@@ -119,15 +118,15 @@ export const About = styled.div`
background: #fff;
color: #000;
border: 1px solid;
- margin-bottom: .5em;
- padding-bottom: .5em;
+ margin-bottom: 0.5em;
+ padding-bottom: 0.5em;
`;
export const AboutTitle = styled.div`
background: #800;
color: #fff;
position: relative;
- padding-left: .5em;
+ padding-left: 0.5em;
line-height: 2em;
h2 {
@@ -141,8 +140,8 @@ export const AboutTitle = styled.div`
export const AboutContent = styled.div`
line-height: 1.5em;
font-size: 93%;
- padding: .5em;
- padding-top: .25em;
+ padding: 0.5em;
+ padding-top: 0.25em;
padding-bottom: 0;
min-height: 91px;
@@ -165,39 +164,92 @@ export const AboutContent = styled.div`
}
`;
-export const Boards = styled.div`
+export const BoardsBox = styled.div`
border: 1px solid;
- margin: 0;
- padding: 0;
- padding-bottom: .5em;
+ padding-bottom: 0.5em;
background: #fff;
- word-wrap: break-word;
margin-bottom: 6px;
- #subscriptions {
- text-align: left;
+ &:after {
+ content: '';
+ display: table;
+ clear: both;
}
-`;
-export const BoardsTitle = styled.div`
- background: #fca;
- color: #800;
- position: relative;
- padding-left: .5em;
- line-height: 2em;
+ .boxbar {
+ background: #fca;
+ color: #800;
+ padding-left: 0.5em;
+ line-height: 2em;
- h2 {
- font-size: 131%;
- font-weight: 700;
- margin: 0;
- padding: 0;
+ h2 {
+ font-size: 131%;
+ font-weight: 700;
+ margin: 0;
+ padding: 0;
+ }
+ }
+
+ .boxcontent {
+ font-size: 93%;
+ padding: 0.5em;
+ padding-top: 0.25em;
+ padding-bottom: 0;
+ line-height: 130%;
+
+ .column {
+ float: left;
+ width: 163px;
+ margin-right: 20px;
+ overflow: hidden;
+
+ h3 {
+ font-size: 100%;
+ font-weight: 700;
+ }
+
+ ul {
+ padding: 0;
+ margin: 0;
+
+ li {
+ list-style: none;
+ white-space: nowrap;
+
+ a {
+ color: #800;
+ text-decoration: none;
+
+ :hover {
+ text-decoration: underline;
+ color: #e00;
+ }
+ }
+ }
+ .disconnected {
+ width: 13px;
+ margin: 0 2px -3px 0;
+ position: relative;
+ }
+ }
+ }
+ #button {
+ color: #800;
+ font-weight: 700;
+ text-decoration: none;
+ cursor: pointer;
+
+ :hover {
+ color: #e00;
+ }
+ }
}
`;
export const BoardsContent = styled.div`
font-size: 93%;
- padding: .5em;
- padding-top: .25em;
+ padding: 0.5em;
+ padding-top: 0.25em;
padding-bottom: 0;
line-height: 130%;
text-align: center;
@@ -233,7 +285,6 @@ export const BoardsContent = styled.div`
position: relative;
}
-
.board-avatar-container img.board-avatar {
width: 100%;
height: 100%;
@@ -265,7 +316,7 @@ export const BoardsContent = styled.div`
:hover {
text-decoration: underline;
- color: #e00
+ color: #e00;
}
}
@@ -280,7 +331,8 @@ export const BoardsContent = styled.div`
}
@media (max-width: 480px) {
- .boardlink, #view-all {
+ .boardlink,
+ #view-all {
font-size: 1.2em;
}
@@ -302,15 +354,13 @@ export const BoardsContent = styled.div`
position: absolute;
}
}
-
-
`;
export const Footer = styled.div`
font-size: 93%;
text-align: center;
clear: both;
- padding-top: .5em;
+ padding-top: 0.5em;
padding-bottom: 2em;
ul {
@@ -320,7 +370,7 @@ export const Footer = styled.div`
width: 750px;
padding: 0;
}
-
+
li {
background: #fed;
display: block;
@@ -331,7 +381,7 @@ export const Footer = styled.div`
margin-top: -1px;
list-style: none;
}
-
+
.fill {
border-top: 1x solid;
border-right: 0;
@@ -340,16 +390,16 @@ export const Footer = styled.div`
background: #ffe;
width: 10.5%;
}
-
+
.first {
border-left: 1px solid;
}
-
+
a {
color: #800;
text-decoration: none;
}
-
+
@media (max-device-width: 640px) {
padding-top: 2em;
padding-bottom: 3em;
@@ -386,4 +436,4 @@ export const Footer = styled.div`
text-decoration: underline;
}
}
-`;
\ No newline at end of file
+`;
diff --git a/src/components/views/Home.jsx b/src/components/views/Home.jsx
index 9da3cdba..6fb56182 100644
--- a/src/components/views/Home.jsx
+++ b/src/components/views/Home.jsx
@@ -1,13 +1,14 @@
-import React, { Fragment, useEffect, useRef, useState } from 'react';
+import React, { useEffect, useRef, useState } from 'react';
import { Helmet } from 'react-helmet-async';
-import { useAccount, useSubplebbit } from '@plebbit/plebbit-react-hooks';
+import { useAccount, useComment, useSubplebbit, useSubplebbits } from '@plebbit/plebbit-react-hooks';
import { Link, useNavigate } from 'react-router-dom';
-import { Container, Header, Logo, Page, Search, About, AboutTitle, AboutContent, Boards, BoardsTitle, BoardsContent, Footer } from '../styled/views/Home.styled';
+import { Container, Header, Logo, Page, Search, About, AboutTitle, AboutContent, BoardsBox, BoardsContent, Footer } from '../styled/views/Home.styled';
import BoardAvatar from '../BoardAvatar';
import OfflineIndicator from '../OfflineIndicator';
import CreateBoardModal from '../modals/CreateBoardModal';
import useGeneralStore from '../../hooks/stores/useGeneralStore';
import packageJson from '../../../package.json';
+import getCommentMediaInfo from '../../utils/getCommentMediaInfo';
import { Tooltip } from 'react-tooltip';
const { version } = packageJson;
const commitRef = process?.env?.REACT_APP_COMMIT_REF ? ` ${process.env.REACT_APP_COMMIT_REF.slice(0, 7)}` : '';
@@ -20,17 +21,64 @@ const SubplebbitTitle = ({ address }) => {
return <>{title ? {title} : }>;
};
+const RecentThread = ({ commentCid }) => {
+ const comment = useComment({ commentCid });
+ const subplebbit = useSubplebbit({ subplebbitAddress: comment?.subplebbitAddress });
+ const { setSelectedAddress, setSelectedTitle } = useGeneralStore((state) => state);
+ const commentMediaInfo = getCommentMediaInfo(comment);
+
+ return (
+
+
+
+
+
+
{
+ setSelectedTitle(subplebbit?.title);
+ setSelectedAddress(comment?.subplebbitAddress);
+ window.scrollTo(0, 0);
+ }}
+ >
+ {commentMediaInfo?.type === 'webpage' && comment.thumbnailUrl ? (
+
+ ) : commentMediaInfo?.type === 'image' ? (
+
+ ) : (
+
+ )}
+
+
+
+
+ {comment?.title ? {comment?.title} : null}
+ {comment?.content ? `: ${comment.content}` : null}
+
+
+ );
+};
+
const Home = () => {
- const { bodyStyle, setBodyStyle, defaultSubplebbits, setSelectedAddress, selectedStyle, setSelectedStyle, setSelectedTitle } = useGeneralStore((state) => state);
+ const { bodyStyle, setBodyStyle, defaultSubplebbits, defaultNsfwSubplebbits, setSelectedAddress, selectedStyle, setSelectedStyle } = useGeneralStore((state) => state);
+
+ const sfwAddresses = defaultSubplebbits.map((subplebbit) => subplebbit.address);
+ const sfwSubs = useSubplebbits({ subplebbitAddresses: sfwAddresses });
+ const sfwList = sfwSubs.subplebbits.map((subplebbit) => subplebbit?.address);
+ const sfwListCids = sfwSubs.subplebbits.map((s) => s?.lastPostCid);
const account = useAccount();
const navigate = useNavigate();
+ const isElectron = window.electron && window.electron.isElectron;
const inputRef = useRef(null);
const prevStyle = useRef(selectedStyle);
const prevBodyStyle = useRef(bodyStyle);
const [isCreateBoardOpen, setIsCreateBoardOpen] = useState(false);
+ const [showAllSFWBoards, setShowAllSFWBoards] = useState(false);
+ const [showAllSubscriptions, setShowAllSubscriptions] = useState(false);
// prevent dark mode
useEffect(() => {
@@ -84,7 +132,7 @@ const Home = () => {
/>
{
const address = inputRef.current.value;
if (address) {
@@ -101,58 +149,112 @@ const Home = () => {
- Plebchan is the only serverless, adminless, fully decentralized 4chan alternative where any pleb can create and own unlimited boards, and nobody can
- take them down. Boards can always find competition, and board owners can monetize freely, so janitors aren't needed. Plebchan is a client for{' '}
+ Plebchan is a serverless, adminless, decentralized 4chan alternative where any pleb can create and own unlimited boards. All data comes from the
plebbit
-
- , a pure P2P protocol and tooling for creating decentralized forum applications. All data is just text, and plebchan's speed depends on how many users
- are{' '}
- {
- alert(
- 'You can seed automatically simply by using the plebchan desktop app, which is a plebbit full node. Download it here:\nhttps://github.com/plebbit/plebchan/releases/latest\n\nIf you are comfortable with the command line, you can use plebbit-cli with the --seed flag:\nhttps://github.com/plebbit/plebbit-cli\n\n',
- );
- }}
- >
- seeding
-
- . Search for any board address above, or feel free to click on a popular board below that interests you and jump right in!{' '}
+ {' '}
+ protocol, it's all text including links from which media is embedded, shared peer-to-peer. Users do not need to register an account before participating
+ in the community. Feel free to click on a board below that interests you and jump right in!{' '}
There are no global rules, each board is completely independent and its owner decides how/if it should be moderated.
-
-
- {' '}
- HOW TO RUN A BOARD (currently via CLI only): (1) have a computer you can use as server, because users will connect to your board
- peer-to-peer; (2) go to{' '}
-
- https://github.com/plebbit/plebbit-cli#install
- {' '}
- and follow the instructions to run your daemon, create a subplebbit (board) and edit its settings; (3) users can always connect to your
- board via its address, but it can be added below by submitting a pull request here:{' '}
-
- https://github.com/plebbit/temporary-default-subplebbits
- {' '}
- this will be automated with a plebbit DAO using the plebbit token.
-
- {account?.subscriptions?.length > 0 ? (
-
-
- Subscriptions
-
-
+
+
+
Boards
+
+
+
- You have subscribed to {account?.subscriptions?.length} board{account?.subscriptions?.length > 1 ? 's' : null}
+
+ Default SFW
+
+
{
+ window.scrollTo(0, 0);
+ }}
+ >
+ [view all]
+
+
+ {sfwList.slice(0, showAllSFWBoards ? undefined : 18).map((address, index) => (
+
+
+ {
+ window.scrollTo(0, 0);
+ }}
+ to={`/p/${address}`}
+ >
+ {address}
+
+
+
+ ))}
+ {sfwList.length > 17 && (
+ setShowAllSFWBoards(!showAllSFWBoards)}>
+ {!showAllSFWBoards ? '... ' : null}
+ {showAllSFWBoards ? '[show less]' : '[show more]'}
+
+ )}
+
+
+
+
+
+ {defaultNsfwSubplebbits.length === 0 && No boards yet in this list. }
+ {defaultNsfwSubplebbits.slice(0, showAllSFWBoards ? undefined : 18).map((subplebbit, index) => (
+
+
+ {
+ window.scrollTo(0, 0);
+ }}
+ to={`/p/${subplebbit.address}`}
+ >
+ {subplebbit.address}
+
+
+
+ ))}
+ {defaultNsfwSubplebbits.length > 17 && (
+ setShowAllSFWBoards(!showAllSFWBoards)}>
+ {!showAllSFWBoards ? '... ' : null}
+ {showAllSFWBoards ? '[show less]' : '[show more]'}
+
+ )}
+
+
+
+
Subscriptions
+
{
window.scrollTo(0, 0);
}}
@@ -160,58 +262,65 @@ const Home = () => {
[view all]
- {account?.subscriptions?.map((subscription, index) => (
-
- {
- window.scrollTo(0, 0);
- }}
- to={`/p/${subscription}`}
- >
-
- {subscription}
-
-
-
-
- ))}
-
-
-
- ) : null}
-
-
- Default Boards
-
+
+ {account?.subscriptions?.length === 0 && Not subscribed to any board. }
+ {account?.subscriptions?.slice(0, showAllSubscriptions ? undefined : 18).map((subscription, index) => (
+
+
+ {
+ window.scrollTo(0, 0);
+ }}
+ to={`/p/${subscription}`}
+ >
+ {subscription}
+
+
+
+ ))}
+ {account?.subscriptions?.length > 17 && (
+ setShowAllSubscriptions(!showAllSubscriptions)}>
+ {!showAllSubscriptions ? '... ' : null}
+ {showAllSubscriptions ? '[show less]' : '[show more]'}
+
+ )}
+
+
+
+
+
+
+
+
Recent Threads
+
- {defaultSubplebbits.map((subplebbit, index) => (
-
-
- {subplebbit.title ?? }
-
-
- {
- setSelectedTitle(subplebbit.title);
- setSelectedAddress(subplebbit.address);
- window.scrollTo(0, 0);
- }}
- >
-
-
-
-
-
- {subplebbit.address}
-
-
+ {sfwListCids.slice(0, 8).map((cid) => (
+
))}
-
+
diff --git a/src/components/views/NotFound.jsx b/src/components/views/NotFound.jsx
index e6815d21..da2b851e 100644
--- a/src/components/views/NotFound.jsx
+++ b/src/components/views/NotFound.jsx
@@ -1,23 +1,22 @@
import React, { useEffect } from 'react';
import { Helmet } from 'react-helmet-async';
-import { Link } from "react-router-dom";
-import { Container, Header, Logo, Page, Boards, BoardsTitle } from '../styled/views/Home.styled';
+import { Link } from 'react-router-dom';
+import { Container, Header, Logo, Page, BoardsBox } from '../styled/views/Home.styled';
import packageJson from '../../../package.json';
-const {version} = packageJson;
+const { version } = packageJson;
const commitRef = process?.env?.REACT_APP_COMMIT_REF ? ` ${process.env.REACT_APP_COMMIT_REF.slice(0, 7)}` : '';
-
const NotFound = () => {
useEffect(() => {
const yotsubaBodyStyle = {
- background: "#ffe url(assets/fade.png) top repeat-x",
- color: "maroon",
- fontFamily: "Arial, Helvetica, sans-serif"
+ background: '#ffe url(assets/fade.png) top repeat-x',
+ color: 'maroon',
+ fontFamily: 'Arial, Helvetica, sans-serif',
};
const originalBodyStyles = {
background: document.body.style.background,
color: document.body.style.color,
- fontFamily: document.body.style.fontFamily
+ fontFamily: document.body.style.fontFamily,
};
for (const [key, value] of Object.entries(yotsubaBodyStyle)) {
@@ -39,34 +38,41 @@ const NotFound = () => {
-
-
+
+
-
-
- 404 Not Found
-
-
-
+
+
+
404 Not Found
+
+
+
-
- plebchan v{version}{commitRef}. GPL-2.0
+
+ plebchan v{version}
+ {commitRef}. GPL-2.0
>
);
-}
+};
-export default NotFound;
\ No newline at end of file
+export default NotFound;
diff --git a/src/hooks/stores/useGeneralStore.js b/src/hooks/stores/useGeneralStore.js
index 83b3b378..116a6ec2 100644
--- a/src/hooks/stores/useGeneralStore.js
+++ b/src/hooks/stores/useGeneralStore.js
@@ -20,6 +20,9 @@ const useGeneralStore = create((set) => ({
defaultSubplebbits: [],
setDefaultSubplebbits: (subplebbits) => set({ defaultSubplebbits: subplebbits }),
+ defaultNsfwSubplebbits: [],
+ setDefaultNsfwSubplebbits: (subplebbits) => set({ defaultNsfwSubplebbits: subplebbits }),
+
deletePost: false,
setDeletePost: (deletePost) => set({ deletePost }),