From 4d1fe1355b22837f5ac58ba971859eea05ea71c6 Mon Sep 17 00:00:00 2001 From: plebbitor Date: Sun, 19 Feb 2023 16:48:46 +0100 Subject: [PATCH] added catalog view --- public/assets/sticky.gif | Bin 0 -> 552 bytes src/App.js | 4 + src/components/Board.jsx | 13 +- src/components/Catalog.jsx | 345 +++++++++++++++++++++++ src/components/styles/Board.styled.jsx | 64 +++++ src/components/styles/Catalog.styled.jsx | 83 ++++++ src/components/styles/Thread.styled.jsx | 5 + 7 files changed, 509 insertions(+), 5 deletions(-) create mode 100644 public/assets/sticky.gif create mode 100644 src/components/Catalog.jsx create mode 100644 src/components/styles/Catalog.styled.jsx diff --git a/public/assets/sticky.gif b/public/assets/sticky.gif new file mode 100644 index 0000000000000000000000000000000000000000..f2f13b96ec6e00f7b4f3beaaf70621dbc714c7bc GIT binary patch literal 552 zcmZ?wbhEHb6krfwc$Uau9J;_FdRgd%Yt|JfjPego-Ex2Vkr#^(J$Fi36FvE=w%7EG z`FEoyUhyd0E@Rg2*>pC1;xz@w$p#?{^XJ{VbLWn#+tiYh60Oi>3->)SDLkqgzFNty zS1(}RtldwO@~6j5z7jp}NfBnffano<=cuwo@@3*fx)x7*>%(RycoH;jxtM)iIoM~PE&^cqhZPw-~Q>GZ_AFNsRaLJ+PY4h&R z+40b}^0;@=c9)zjew`P}%gaxkIAM{pKBV@5ch34n2cLVlo^wc97u0*Xd()%DsaGmi zKWb`f>fiA~(x`=jmO$|*3nK$VB7+Xd5>T8lu(viOHZ`||sZcXkHE#*ajcj3D$-x?}Ai%^V?9d#hYVFN#&l%;;u&-I)!qHY%Q9dxl zpkK(3M=vJAg=?OSGQX2Kv*|nyZ5C!-Pw8n|B66ZelH$|MbUc~_rTF^ef|Xhl`o%PZ P=C!D({}JS3VXy`O-}1^q literal 0 HcmV?d00001 diff --git a/src/App.js b/src/App.js index 74f6c2d0..daf4725d 100644 --- a/src/App.js +++ b/src/App.js @@ -4,6 +4,7 @@ import { Helmet } from 'react-helmet-async'; import Home from './components/Home'; import Board from './components/Board'; import Thread from './components/Thread'; +import Catalog from './components/Catalog'; import { createGlobalStyle } from 'styled-components'; export const BoardContext = React.createContext(); @@ -53,6 +54,9 @@ export default function App() { }> } /> + }> + } /> + diff --git a/src/components/Board.jsx b/src/components/Board.jsx index 1f84a520..851dcf0c 100644 --- a/src/components/Board.jsx +++ b/src/components/Board.jsx @@ -19,7 +19,7 @@ const Board = ({ setBodyStyle }) => { const { feed, hasMore, loadMore } = useFeed([`${selectedAddress}`], 'new'); - console.log(feed); + // console.log(feed); const tryLoadMore = async () => { try {loadMore()} @@ -216,7 +216,7 @@ const Board = ({ setBodyStyle }) => { const { replyCount, replies: { pages: { topAll: { comments: nestedComments } } } } = comment; if (replyCount > 0 && nestedComments.length > 0) { - const renderedNestedComments = renderComments(nestedComments, comment.cid); + const renderedNestedComments = renderComments(nestedComments); return [comment, ...renderedNestedComments]; } @@ -317,6 +317,9 @@ const Board = ({ setBodyStyle }) => { + [ + Catalog + ]
@@ -331,7 +334,7 @@ const Board = ({ setBodyStyle }) => { let counter = 1; const thread = object; const { replies: { pages: { topAll: { comments } } } } = object; - const renderedComments = renderComments(comments, thread.cid); + const renderedComments = renderComments(comments); return ( <>
@@ -406,8 +409,8 @@ const Board = ({ setBodyStyle }) => {
- - {`>>${counterString}`}{counterString !== '' &&
} +
+ {`>>${counterString}`}{
}
{reply.content}
diff --git a/src/components/Catalog.jsx b/src/components/Catalog.jsx new file mode 100644 index 00000000..40b81f07 --- /dev/null +++ b/src/components/Catalog.jsx @@ -0,0 +1,345 @@ +import React, { useState, useEffect, useContext } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import { Container, NavBar, Header, Break, PostForm, PostFormLink, PostFormTable } from './styles/Board.styled'; +import { TopBar } from './styles/Thread.styled'; +import { Threads } from './styles/Catalog.styled'; +import { BoardContext } from '../App'; +import { useFeed, useAccountsActions } from '@plebbit/plebbit-react-hooks'; +import ImageBanner from './ImageBanner'; +import InfiniteScroll from 'react-infinite-scroller'; + +const Catalog = ({ setBodyStyle }) => { + const [defaultSubplebbits, setDefaultSubplebbits] = useState([]); + const { selectedTitle, setSelectedTitle, selectedAddress, setSelectedAddress, selectedStyle, setSelectedStyle } = useContext(BoardContext); + const [showPostFormLink, setShowPostFormLink] = useState(true); + const [showPostForm, setShowPostForm] = useState(false); + const [name, setName] = useState(''); + const [subject, setSubject] = useState(''); + const [comment, setComment] = useState(''); + const navigate = useNavigate(); + + const { feed, hasMore, loadMore } = useFeed([`${selectedAddress}`], 'new'); + + const tryLoadMore = async () => { + try {loadMore()} + catch (e) + {await new Promise(resolve => setTimeout(resolve, 1000))} + } + + const { publishComment } = useAccountsActions(); + + const onChallengeVerification = (challengeVerification) => { + if (challengeVerification.challengeSuccess === true) { + console.log('challenge success', {publishedCid: challengeVerification.publication.cid}) + } + else if (challengeVerification.challengeSuccess === false) { + console.error('challenge failed', {reason: challengeVerification.reason, errors: challengeVerification.errors}); + alert("Error: You seem to have mistyped the CAPTCHA. Please try again."); + } + } + + const onChallenge = async (challenges, comment) => { + let challengeAnswers = []; + try { + challengeAnswers = await getChallengeAnswersFromUser(challenges) + } + catch (error) { + console.log(error); + } + if (challengeAnswers) { + await comment.publishChallengeAnswers(challengeAnswers) + } + } + + const onError = (error) => console.error(error) + + const getChallengeAnswersFromUser = async (challenges) => { + return new Promise((resolve, reject) => { + const imageString = challenges?.challenges[0].challenge; + const imageSource = `data:image/png;base64,${imageString}`; + const challengeImg = new Image(); + challengeImg.src = imageSource; + + challengeImg.onload = () => { + const inputEl = document.getElementById('t-resp'); + const cntEl = document.getElementById('t-cnt'); + cntEl.appendChild(challengeImg); + inputEl.focus(); + + const handleKeyDown = (event) => { + if (event.key === 'Enter') { + const challengeResponse = inputEl.value; + inputEl.value = ''; + + if (cntEl.contains(challengeImg)) { + cntEl.removeChild(challengeImg); + } + + document.removeEventListener('keydown', handleKeyDown); + + resolve(challengeResponse); + } + }; + + document.addEventListener('keydown', handleKeyDown); + }; + + challengeImg.onerror = () => { + reject(new Error('Could not load challenge image')); + }; + }); + }; + + useEffect(() => { + let didCancel = false; + fetch( + "https://raw.githubusercontent.com/plebbit/temporary-default-subplebbits/master/subplebbits.json", + { cache: "no-cache" } + ) + .then((res) => res.json()) + .then(res => { + if (!didCancel) { + setDefaultSubplebbits(res); + } + }); + return () => { + didCancel = true; + }; + }, []); + + const handleVoidClick = () => {} + + const handleClick = (title, address) => { + setSelectedTitle(title); + setSelectedAddress(address); + }; + + const handleClickHelp = () => { + alert("- The CAPTCHA loads after you click \"Post\" \n- The CAPTCHA is case-sensitive. \n- Make sure to not block any cookies set by plebchan."); + }; + + const handleClickForm = () => { + setShowPostFormLink(false); + setShowPostForm(true); + navigate('/board/catalog/post-thread'); + }; + + const handlePublishComment = async () => { + // Event.preventDefault(); + try { + const pendingComment = await publishComment({ + content: comment, + title: subject, + subplebbitAddress: selectedAddress, + onChallengeVerification, + onChallenge, + onError, + }); + console.log(`Comment pending with index: ${pendingComment.index}`); + setName(''); + setSubject(''); + setComment(''); + } catch (error) { + console.error(error); + } + }; + + + const handleStyleChange = (event) => { + switch (event.target.value) { + case "Yotsuba": + setBodyStyle({ + background: "#ffe url(/assets/fade.png) top repeat-x", + color: "maroon", + fontFamily: "Arial, Helvetica, sans-serif" + }); + setSelectedStyle("Yotsuba"); + break; + + case "Yotsuba B": + setBodyStyle({ + background: "#eef2ff url(/assets/fade-blue.png) top center repeat-x", + color: "#000", + fontFamily: "Arial, Helvetica, sans-serif" + }); + setSelectedStyle("Yotsuba B"); + break; + + case "Futaba": + setBodyStyle({ + background: "#ffe", + color: "maroon", + fontFamily: "times new roman, serif" + }); + setSelectedStyle("Futaba"); + break; + + case "Burichan": + setBodyStyle({ + background: "#eef2ff", + color: "#000", + fontFamily: "times new roman, serif" + }); + setSelectedStyle("Burichan"); + break; + + case "Tomorrow": + setBodyStyle({ + background: "#1d1f21 none", + color: "#c5c8c6", + fontFamily: "Arial, Helvetica, sans-serif" + }); + setSelectedStyle("Tomorrow"); + break; + + case "Photon": + setBodyStyle({ + background: "#eee none", + color: "#333", + fontFamily: "Arial, Helvetica, sans-serif" + }); + setSelectedStyle("Photon"); + break; + + default: + setBodyStyle({ + background: "#ffe url(/assets/fade.png) top repeat-x", + color: "maroon", + fontFamily: "Arial, Helvetica, sans-serif" + }); + setSelectedStyle("Yotsuba"); + } + } + + return ( + + + <> + {defaultSubplebbits.map(subplebbit => ( + + [ + handleClick(subplebbit.title, subplebbit.address)} + >{subplebbit.title} + ]  + + ))} + [ + handleStyleChange({target: {value: "Yotsuba"}} + )}>Home]  + + + +
+ <> +
+ +
+ <> +
{selectedTitle}
+
p/{selectedAddress}
+ + +
+ + + + [ + Start a New Thread + ] + + + + + Name + + setName(event.target.value)} /> + + + + Subject + + setSubject(event.target.value)} /> + + + + + Comment + + + + + + Verification + +
+ + +
+
+
+ + + + Embed File + + + + + + +
+
+ +
+ + Style: +   + + + + [ + Return + ] + +
+
+ + Loading...} + > + {feed.map(thread => { + return ( +
+ + + +
+ +
+
+ R: + {thread.replyCount} +
+
+ {thread.title ? `${thread.title}` : null} + {thread.content ? `${thread.content}` : null} +
+
+ )})} +
+
+
+ ); +} + +export default Catalog; \ No newline at end of file diff --git a/src/components/styles/Board.styled.jsx b/src/components/styles/Board.styled.jsx index d526b959..2ec467ef 100644 --- a/src/components/styles/Board.styled.jsx +++ b/src/components/styles/Board.styled.jsx @@ -1009,7 +1009,17 @@ export const TopBar = styled.div` .style-changer { margin-left: 5px; + margin-right: 10px; font-size: 10pt; + } + + a { + color: #00e; + text-decoration: none; + } + + a:hover { + color: red; }`; case 'Yotsuba B': @@ -1022,29 +1032,63 @@ export const TopBar = styled.div` .style-changer { margin-left: 5px; + margin-right: 10px; font-size: 10pt; + } + + a { + color: #34345c; + text-decoration: none; + } + + a:hover { + color: red; }`; case 'Futaba': return `clear: both; + font-size: 12pt; + hr { clear: both; } .style-changer { margin-left: 5px; + margin-right: 10px; font-size: 12pt; + } + + a { + color: #00e; + text-decoration: underline; + } + + a:hover { + color: red; }`; case 'Burichan': return `clear: both; + font-size: 12pt; + hr { clear: both; } .style-changer { margin-left: 5px; + margin-right: 10px; font-size: 12pt; + } + + a { + color: #34345c; + text-decoration: underline; + } + + a:hover { + color: red; }`; case 'Tomorrow': @@ -1057,7 +1101,17 @@ export const TopBar = styled.div` .style-changer { margin-left: 5px; + margin-right: 10px; font-size: 10pt; + } + + a { + color: #81a2be; + text-decoration: none; + } + + a:hover { + color: #5f89ab; }`; case 'Photon': @@ -1070,7 +1124,17 @@ export const TopBar = styled.div` .style-changer { margin-left: 5px; + margin-right: 10px; font-size: 10pt; + } + + a { + color: #f60; + text-decoration: none; + } + + a:hover { + color: red; }`; } diff --git a/src/components/styles/Catalog.styled.jsx b/src/components/styles/Catalog.styled.jsx new file mode 100644 index 00000000..6753d6d5 --- /dev/null +++ b/src/components/styles/Catalog.styled.jsx @@ -0,0 +1,83 @@ +import styled from 'styled-components'; + +export const Threads = styled.div` + padding: 20px 0; + text-align: center; + margin: 0; + + .thread { + width: 180px; + max-height: 320px; + vertical-align: top; + display: inline-block; + word-wrap: break-word; + overflow: hidden; + margin-top: 5px; + margin-bottom: 20px; + padding: 5px 0 3px; + position: relative; + } + + img { + max-width: 150px; + max-height: 150px; + display: inline; + margin: auto; + box-shadow: 0 0 5px rgba(0, 0, 0, .25); + min-height: 50px; + min-width: 50px; + border: 0; + } + + .thread-icons { + display: inline; + height: 16px; + margin: 2px 0 0 -101px; + position: absolute; + width: 100px; + text-align: right; + } + + .sticky-icon { + background-image: url(/assets/sticky.gif); + width: 16px; + height: 16px; + display: inline-block; + } + + .meta { + cursor: help; + font-size: 11px; + line-height: 8px; + margin-top: 2px; + margin-bottom: 1px; + } + + .teaser { + display: block; + padding: 0 15px; + } + + ${({ selectedStyle }) => { + switch (selectedStyle) { + case 'Yotsuba': + return ``; + + case 'Yotsuba B': + return ``; + + case 'Futaba': + return `font-size: 12pt;`; + + case 'Burichan': + return `font-size: 12pt;`; + + case 'Tomorrow': + return ``; + + case 'Photon': + return ``; + + } + }} +`; \ No newline at end of file diff --git a/src/components/styles/Thread.styled.jsx b/src/components/styles/Thread.styled.jsx index 5ffacdf7..a76ecd96 100644 --- a/src/components/styles/Thread.styled.jsx +++ b/src/components/styles/Thread.styled.jsx @@ -471,6 +471,9 @@ export const ReplyFormTable = styled.table` `; export const TopBar = styled.div` + .return-button { + padding-top: 3px; + } ${({ selectedStyle }) => { switch (selectedStyle) { case 'Yotsuba': @@ -541,6 +544,7 @@ export const TopBar = styled.div` .return-button { float: right; margin-right: 4px; + padding-top: 2px; font-size: 12pt; a, a:visited { @@ -566,6 +570,7 @@ export const TopBar = styled.div` .return-button { float: right; margin-right: 4px; + padding-top: 2px; font-size: 12pt; a, a:visited {