From 9afd658de736a066a41646894d0c1c2ced4df503 Mon Sep 17 00:00:00 2001 From: "plebeius.eth" Date: Tue, 27 Jun 2023 21:06:30 +0200 Subject: [PATCH] add description and rules components --- src/App.js | 8 + src/components/views/Board.jsx | 8 +- src/components/views/Catalog.jsx | 7 +- src/components/views/Description.jsx | 494 +++++++++++++++++++++++++++ src/components/views/Rules.jsx | 467 +++++++++++++++++++++++++ 5 files changed, 976 insertions(+), 8 deletions(-) create mode 100644 src/components/views/Description.jsx create mode 100644 src/components/views/Rules.jsx diff --git a/src/App.js b/src/App.js index c6bfd26d..9b9ab199 100755 --- a/src/App.js +++ b/src/App.js @@ -10,9 +10,11 @@ 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 Description from './components/views/Description'; import Home from './components/views/Home'; import NotFound from './components/views/NotFound'; import Pending from './components/views/Pending'; +import Rules from './components/views/Rules'; import Subscriptions from './components/views/Subscriptions'; import SubscriptionsCatalog from './components/views/SubscriptionsCatalog'; import Thread from './components/views/Thread'; @@ -169,6 +171,12 @@ export default function App() { } /> } /> + }> + } /> + + }> + } /> + }> } /> diff --git a/src/components/views/Board.jsx b/src/components/views/Board.jsx index 0bf149f0..0abe590a 100755 --- a/src/components/views/Board.jsx +++ b/src/components/views/Board.jsx @@ -830,7 +830,7 @@ const Board = () => { Closed   [ - {}} style={{textDecoration: "none"}} className="reply-link">Reply + Reply ] {
- {}} className="button-mobile">View Thread + View Thread
@@ -969,7 +969,7 @@ const Board = () => { Closed   [ - {}} style={{textDecoration: "none"}} className="reply-link">Reply + Reply ] {
- {}} className="button-mobile">View Thread + View Thread
diff --git a/src/components/views/Catalog.jsx b/src/components/views/Catalog.jsx index bf5dadac..95ff9e8a 100755 --- a/src/components/views/Catalog.jsx +++ b/src/components/views/Catalog.jsx @@ -753,8 +753,7 @@ const Catalog = () => { , document.body )} - {}} - onClick={() => setSelectedThread("rules")}> +
Rules {": " + subplebbit.rules.map((rule, index) => `${index + 1}. ${rule}`).join(' ')} @@ -766,7 +765,7 @@ const Catalog = () => {
setIsHoveringOnThread('description')} onMouseLeave={() => setIsHoveringOnThread('')}> - {}}> + {subplebbit.suggested?.avatarUrl ? ( board avatar @@ -860,7 +859,7 @@ const Catalog = () => { , document.body )} - {}} + setSelectedThread("description")}>
Welcome to {subplebbit.title || subplebbit.address} diff --git a/src/components/views/Description.jsx b/src/components/views/Description.jsx new file mode 100644 index 00000000..c61204fa --- /dev/null +++ b/src/components/views/Description.jsx @@ -0,0 +1,494 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { Helmet } from 'react-helmet-async'; +import { Link, useNavigate, useParams } from 'react-router-dom'; +import { Tooltip } from 'react-tooltip'; +import { useSubplebbit } from '@plebbit/plebbit-react-hooks'; +import { debounce } from 'lodash'; +import { Container, NavBar, Header, Break, PostMenu, PostForm } from '../styled/views/Board.styled'; +import { TopBar, BoardForm, Footer, ReplyFormLink} from '../styled/views/Thread.styled'; +import { PostMenuCatalog } from '../styled/views/Catalog.styled'; +import ImageBanner from '../ImageBanner'; +import OfflineIndicator from '../OfflineIndicator'; +import SettingsModal from '../modals/SettingsModal'; +import getDate from '../../utils/getDate'; +import handleImageClick from '../../utils/handleImageClick'; +import handleStyleChange from '../../utils/handleStyleChange'; +import useGeneralStore from '../../hooks/stores/useGeneralStore'; +import packageJson from '../../../package.json' +const {version} = packageJson + + +const Description = () => { + const { + defaultSubplebbits, + isSettingsOpen, setIsSettingsOpen, + selectedAddress, setSelectedAddress, + selectedStyle, + selectedThread, setSelectedThread, + setSelectedTitle, + } = useGeneralStore(state => state); + + const navigate = useNavigate(); + + const threadMenuRefs = useRef({}); + const postMenuRef = useRef(null); + const postMenuCatalogRef = useRef(null); + + const [prevScrollPos, setPrevScrollPos] = useState(0); + const [visible, setVisible] = useState(true); + const [menuPosition, setMenuPosition] = useState({top: 0, left: 0}); + const [openMenuCid, setOpenMenuCid] = useState(null); + + const { subplebbitAddress, threadCid } = useParams(); + const subplebbit = useSubplebbit({subplebbitAddress: selectedAddress}); + + + const handleOptionClick = () => { + setOpenMenuCid(null); + }; + + const handleOutsideClick = useCallback((e) => { + if (openMenuCid !== null && !postMenuRef.current.contains(e.target) && !postMenuCatalogRef.current.contains(e.target)) { + setOpenMenuCid(null); + } + }, [openMenuCid, postMenuRef, postMenuCatalogRef]); + + + useEffect(() => { + if (openMenuCid !== null) { + document.addEventListener('click', handleOutsideClick); + } else { + document.removeEventListener('click', handleOutsideClick); + } + + return () => { + document.removeEventListener('click', handleOutsideClick); + }; + }, [openMenuCid, handleOutsideClick]); + + + useEffect(() => { + window.scrollTo(0, 0); + }, []); + + + // temporary title from JSON, gets subplebbitAddress and threadCid from URL + useEffect(() => { + setSelectedAddress(subplebbitAddress); + setSelectedThread(threadCid); + const selectedSubplebbit = defaultSubplebbits.find((subplebbit) => subplebbit.address === subplebbitAddress); + if (selectedSubplebbit) { + setSelectedTitle(selectedSubplebbit.title); + } + }, [subplebbitAddress, setSelectedAddress, setSelectedTitle, defaultSubplebbits, setSelectedThread, threadCid]); + + // mobile navbar scroll effect + useEffect(() => { + const debouncedHandleScroll = debounce(() => { + const currentScrollPos = window.scrollY; + setVisible(prevScrollPos > currentScrollPos || currentScrollPos < 10); + setPrevScrollPos(currentScrollPos); + }, 50); + + window.addEventListener('scroll', debouncedHandleScroll); + + return () => window.removeEventListener('scroll', debouncedHandleScroll); + }, [prevScrollPos, visible]); + + // 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 ( + <> + + + {"Board Description - " + (subplebbit.title || subplebbit.address) + " - plebchan"} + + + + setIsSettingsOpen(false)} /> + + <> + + [ + All +  /  + Subscriptions + ] [ + {defaultSubplebbits.map((subplebbit, index) => ( + + {index === 0 ? null : "\u00a0"} + { + setSelectedTitle(subplebbit.title); + setSelectedAddress(subplebbit.address); + }} + >{subplebbit.title ? subplebbit.title : subplebbit.address} + {index !== defaultSubplebbits.length - 1 ? " /" : null} + + ))} + ] + + + [ + 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 + ] + [ + setIsSettingsOpen(true)}>Settings + ] + [ + Home + ] + +
+
+
+ Board +   +   + 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 +
+
+ setIsSettingsOpen(true)}>Settings +   + {handleStyleChange({target: {value: "Yotsuba"}}); window.scrollTo(0, 0);}}>Home +
+
+
+
 
+
 
+ +
+
+ <> +
+ +
+ <> +
{subplebbit.title ?? null}
+
p/{subplebbit.address} + +
+ + +
+ + + +
+ {window.scrollTo(0, 0)}}> + Return + +
+
+ + {window.scrollTo(0, 0)}}>Catalog + +
+
+ + window.scrollTo(0, document.body.scrollHeight)} onMouseOver={(event) => event.target.style.cursor='pointer'}>Bottom + +
+
+
+ + + Style: +   + + + + [ + Return + ] + + + [ + Catalog + ] + + + [ + window.scrollTo(0, document.body.scrollHeight)} + onMouseOver={(event) => event.target.style.cursor='pointer'} + onTouchStart={() => window.scrollTo(0, document.body.scrollHeight)}>Bottom + ] + + + + + <> +
+
+
+
+
+ {subplebbit.suggested?.avatarUrl ? ( + + ) : null} + + Welcome to {subplebbit.title || subplebbit.address} +   + ## Board Admins +   + {getDate(subplebbit.createdAt)} +   + Sticky +   + Closed +   + [ + {}} style={{textDecoration: "none"}} className="reply-link">Reply + ] + + { + threadMenuRefs.current[subplebbit.pubsubTopic] = el; + }} + className='post-menu-button' + rotated={openMenuCid === subplebbit.pubsubTopic} + onClick={(event) => { + event.stopPropagation(); + const rect = threadMenuRefs.current[subplebbit.pubsubTopic].getBoundingClientRect(); + setMenuPosition({top: rect.top + window.scrollY, left: rect.left}); + setOpenMenuCid(prevCid => (prevCid === subplebbit.pubsubTopic ? null : subplebbit.pubsubTopic)); + }} + > + ▶ + + {createPortal( + {postMenuCatalogRef.current = el}} + onClick={(event) => event.stopPropagation()} + style={{position: "absolute", + top: menuPosition.top + 7, + left: menuPosition.left}}> +
+
    +
  • handleOptionClick(subplebbit.pubsubTopic)}>Hide thread
  • + {/* {isModerator ? ( + <> + change description + + ) : null} */} +
+
+
, document.body + )} +
+
+
+ {subplebbit.description} +
+
+
+
+
+
+
+
+
+
+
+ + + ## Board Admins +   + + Sticky +   + Closed + +
+ Welcome to {subplebbit.title || subplebbit.address} +   +
+ {getDate(subplebbit.createdAt)} +   +
+ {subplebbit.suggested?.avatarUrl ? ( +
+
+ + board avatar +
image
+
+
+
+ ) : null} +
+
+ {subplebbit.description} +
+
+
+
+ {}} className="button-mobile">View Thread +
+
+
+ +
+
+ + + + Style: +   + + + + <> + + [ + All +  /  + Subscriptions + ]  + + {defaultSubplebbits.map((subplebbit, index) => ( + + {index === 0 ? null : "\u00a0"} + { + setSelectedTitle(subplebbit.title); + setSelectedAddress(subplebbit.address); + }} + >{subplebbit.title ? subplebbit.title : subplebbit.address} + {index !== defaultSubplebbits.length - 1 ? " /" : null} + + ))} + + [ + 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 + ] + [ + setIsSettingsOpen(true)}>Settings + ] + [ + handleStyleChange({target: {value: "Yotsuba"}} + )}>Home + ] + + + +
+ plebchan v{version}. GPL-2.0 +
+
+ About +  •  + App +  •  + Twitter +  •  + Telegram +
+
+
+ + ); +} + +export default Description; \ No newline at end of file diff --git a/src/components/views/Rules.jsx b/src/components/views/Rules.jsx new file mode 100644 index 00000000..ceb2fdfb --- /dev/null +++ b/src/components/views/Rules.jsx @@ -0,0 +1,467 @@ +import React, { useCallback, useEffect, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; +import { Helmet } from 'react-helmet-async'; +import { Link, useNavigate, useParams } from 'react-router-dom'; +import { Tooltip } from 'react-tooltip'; +import { useSubplebbit } from '@plebbit/plebbit-react-hooks'; +import { debounce } from 'lodash'; +import { Container, NavBar, Header, Break, PostMenu, PostForm } from '../styled/views/Board.styled'; +import { TopBar, BoardForm, Footer, ReplyFormLink} from '../styled/views/Thread.styled'; +import { PostMenuCatalog } from '../styled/views/Catalog.styled'; +import ImageBanner from '../ImageBanner'; +import OfflineIndicator from '../OfflineIndicator'; +import SettingsModal from '../modals/SettingsModal'; +import getDate from '../../utils/getDate'; +import handleStyleChange from '../../utils/handleStyleChange'; +import useGeneralStore from '../../hooks/stores/useGeneralStore'; +import packageJson from '../../../package.json' +const {version} = packageJson + + +const Rules = () => { + const { + defaultSubplebbits, + isSettingsOpen, setIsSettingsOpen, + selectedAddress, setSelectedAddress, + selectedStyle, + selectedThread, setSelectedThread, + setSelectedTitle, + } = useGeneralStore(state => state); + + const navigate = useNavigate(); + + const threadMenuRefs = useRef({}); + const postMenuRef = useRef(null); + const postMenuCatalogRef = useRef(null); + + const [prevScrollPos, setPrevScrollPos] = useState(0); + const [visible, setVisible] = useState(true); + const [menuPosition, setMenuPosition] = useState({top: 0, left: 0}); + const [openMenuCid, setOpenMenuCid] = useState(null); + + const { subplebbitAddress, threadCid } = useParams(); + const subplebbit = useSubplebbit({subplebbitAddress: selectedAddress}); + + + const handleOptionClick = () => { + setOpenMenuCid(null); + }; + + const handleOutsideClick = useCallback((e) => { + if (openMenuCid !== null && !postMenuRef.current.contains(e.target) && !postMenuCatalogRef.current.contains(e.target)) { + setOpenMenuCid(null); + } + }, [openMenuCid, postMenuRef, postMenuCatalogRef]); + + + useEffect(() => { + if (openMenuCid !== null) { + document.addEventListener('click', handleOutsideClick); + } else { + document.removeEventListener('click', handleOutsideClick); + } + + return () => { + document.removeEventListener('click', handleOutsideClick); + }; + }, [openMenuCid, handleOutsideClick]); + + + useEffect(() => { + window.scrollTo(0, 0); + }, []); + + + // temporary title from JSON, gets subplebbitAddress and threadCid from URL + useEffect(() => { + setSelectedAddress(subplebbitAddress); + setSelectedThread(threadCid); + const selectedSubplebbit = defaultSubplebbits.find((subplebbit) => subplebbit.address === subplebbitAddress); + if (selectedSubplebbit) { + setSelectedTitle(selectedSubplebbit.title); + } + }, [subplebbitAddress, setSelectedAddress, setSelectedTitle, defaultSubplebbits, setSelectedThread, threadCid]); + + // mobile navbar scroll effect + useEffect(() => { + const debouncedHandleScroll = debounce(() => { + const currentScrollPos = window.scrollY; + setVisible(prevScrollPos > currentScrollPos || currentScrollPos < 10); + setPrevScrollPos(currentScrollPos); + }, 50); + + window.addEventListener('scroll', debouncedHandleScroll); + + return () => window.removeEventListener('scroll', debouncedHandleScroll); + }, [prevScrollPos, visible]); + + // 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 ( + <> + + + {"Board Rules - " + (subplebbit.title || subplebbit.address) + " - plebchan"} + + + + setIsSettingsOpen(false)} /> + + <> + + [ + All +  /  + Subscriptions + ] [ + {defaultSubplebbits.map((subplebbit, index) => ( + + {index === 0 ? null : "\u00a0"} + { + setSelectedTitle(subplebbit.title); + setSelectedAddress(subplebbit.address); + }} + >{subplebbit.title ? subplebbit.title : subplebbit.address} + {index !== defaultSubplebbits.length - 1 ? " /" : null} + + ))} + ] + + + [ + 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 + ] + [ + setIsSettingsOpen(true)}>Settings + ] + [ + Home + ] + +
+
+
+ Board +   +   + 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 +
+
+ setIsSettingsOpen(true)}>Settings +   + {handleStyleChange({target: {value: "Yotsuba"}}); window.scrollTo(0, 0);}}>Home +
+
+
+
 
+
 
+ +
+
+ <> +
+ +
+ <> +
{subplebbit.title ?? null}
+
p/{subplebbit.address} + +
+ + +
+ + + +
+ {window.scrollTo(0, 0)}}> + Return + +
+
+ + {window.scrollTo(0, 0)}}>Catalog + +
+
+ + window.scrollTo(0, document.body.scrollHeight)} onMouseOver={(event) => event.target.style.cursor='pointer'}>Bottom + +
+
+
+ + + Style: +   + + + + [ + Return + ] + + + [ + Catalog + ] + + + [ + window.scrollTo(0, document.body.scrollHeight)} + onMouseOver={(event) => event.target.style.cursor='pointer'} + onTouchStart={() => window.scrollTo(0, document.body.scrollHeight)}>Bottom + ] + + + + + <> +
+
+
+
+
+ + Rules +   + ## Board Admins +   + {getDate(subplebbit.createdAt)} +   + Sticky +   + Closed +   + [ + {}} style={{textDecoration: "none"}} className="reply-link">Reply + ] + + { + threadMenuRefs.current["rules"] = el; + }} + className='post-menu-button' + rotated={openMenuCid === "rules"} + onClick={(event) => { + event.stopPropagation(); + const rect = threadMenuRefs.current["rules"].getBoundingClientRect(); + setMenuPosition({top: rect.top + window.scrollY, left: rect.left}); + setOpenMenuCid(prevCid => (prevCid === "rules" ? null : "rules")); + }} + > + ▶ + + {createPortal( + {postMenuCatalogRef.current = el}} + onClick={(event) => event.stopPropagation()} + style={{position: "absolute", + top: menuPosition.top + 7, + left: menuPosition.left}}> +
+
    +
  • handleOptionClick("rules")}>Hide thread
  • + {/* {isModerator ? ( + <> + change rules + + ) : null} */} +
+
+
, document.body + )} +
+
+
+ {subplebbit.rules?.map((rule, index) => ( + + {index + 1}. {rule} +
+
+ ))} +
+
+
+
+
+
+
+
+
+
+
+ + + ## Board Admins +   + + Sticky +   + Closed + +
+ Rules +   +
+ {getDate(subplebbit.createdAt)} +   +
+
+
+ {subplebbit.rules?.map((rule, index) => ( + + {index + 1}. {rule} +
+
+ ))} +
+
+
+
+ {}} className="button-mobile">View Thread +
+
+
+ +
+ +
+ + ); +} + +export default Rules; \ No newline at end of file