From cfa1489b16453a6f60af794b556f2de66d686495 Mon Sep 17 00:00:00 2001 From: Tom Date: Thu, 25 May 2023 13:42:58 +0200 Subject: [PATCH] add post menu UI, mod logic, styling to catalog --- src/components/styled/GlobalStyle.styled.jsx | 40 +++ .../styled/views/Catalog.styled.jsx | 167 +++++++++ src/components/views/Catalog.jsx | 330 +++++++++++++++--- 3 files changed, 492 insertions(+), 45 deletions(-) diff --git a/src/components/styled/GlobalStyle.styled.jsx b/src/components/styled/GlobalStyle.styled.jsx index aed763c3..adc961e4 100644 --- a/src/components/styled/GlobalStyle.styled.jsx +++ b/src/components/styled/GlobalStyle.styled.jsx @@ -54,4 +54,44 @@ export const GlobalStyle = createGlobalStyle` max-height: 100% !important; } } + + .post-menu-catalog { + position: absolute; + font-size: 12px; + line-height: 1.3em; + list-style: none; + + ul { + padding: 0; + margin: 0; + white-space: nowrap; + } + + li { + cursor: pointer; + position: relative; + padding: 2px 4px; + } + + .dropdown { + position: relative; + display: inline-block; + } + + .dropdown-menu { + display: none; + position: absolute; + left: 100%; + top: 0; + } + + .dropdown:hover .dropdown-menu { + display: block; + } + + a { + text-decoration: none; + color: inherit; + } + } `; \ No newline at end of file diff --git a/src/components/styled/views/Catalog.styled.jsx b/src/components/styled/views/Catalog.styled.jsx index d1b58403..786a59da 100644 --- a/src/components/styled/views/Catalog.styled.jsx +++ b/src/components/styled/views/Catalog.styled.jsx @@ -77,4 +77,171 @@ export const Threads = styled.div` display: block; padding: 0 15px; } +`; + +export const PostMenuCatalog = styled.div` + .post-menu-catalog { + position: relative; + font-size: 12px; + padding: 0; + + li { + cursor: pointer; + position: relative; + padding: 2px 4px; + } + + .dropdown { + position: relative; + display: inline-block; + } + + .dropdown-menu { + display: none; + position: absolute; + } + + .dropdown:hover .dropdown-menu { + display: block; + } + + a { + text-decoration: none; + color: inherit; + } + } + + ${({ selectedStyle }) => { + switch (selectedStyle) { + case 'Yotsuba': + return `.highlighted { + background-color: #f0c0b0 !important; + border: 1px solid #d99f91 !important; + border-left: none !important; + border-top: none !important; + } + + .post-menu-catalog { + border-right: 1px solid #d9bfb7; + border-bottom: 2px solid #d9bfb7; + + li { + border: 1px solid #d9bfb7; + border-bottom: none; + background-color: #f0e0d6; + + :hover { + background-color: #ffe; + } + } + }`; + + case 'Yotsuba-B': + return `.highlighted { + background-color: #d6bad0 !important; + border: 1px solid #ba9dbf !important; + border-left: none !important; + border-top: none !important; + } + + .post-menu-catalog { + border-right: 1px solid #b7c5d9; + border-bottom: 2px solid #b7c5d9; + + li { + border: 1px solid #b7c5d9; + border-bottom: none; + background-color: #d6daf0; + + :hover { + background-color: #eef2ff; + } + } + }`; + + case 'Futaba': + return `.highlighted { + background-color: #f0c0b0 !important; + } + + .post-menu-catalog { + font-size: 13px !important; + border: 1px solid #d9bfb7; + border-bottom: none; + + li { + background-color: #f0e0d6; + border-bottom: 1px solid #d9bfb7; + + :hover { + background-color: #ffe; + } + } + }`; + + case 'Burichan': + return `.highlighted { + background-color: #d6bad0 !important; + } + + .post-menu-catalog { + font-size: 13px !important; + border: 1px solid #b7c5d9; + border-bottom: none; + + li { + background-color: #d6daf0; + border-bottom: 1px solid #b7c5d9; + + :hover { + background-color: #eef2ff; + } + } + }`; + + case 'Tomorrow': + return `.highlighted { + background-color: #1d1d21 !important; + border: 1px solid #111 !important; + } + + .post-menu-catalog { + border: 1px solid #000; + border-bottom: none; + + li { + background-color: #282a2e; + border-bottom: 1px solid #000; + + :hover { + background-color: #1d1f21; + } + } + }`; + + case 'Photon': + return `.highlighted { + background-color: #ccc !important; + border: 1px solid #ccc !important; + } + + .post-menu-catalog { + border: 1px solid #ccc; + border-bottom: none; + + li { + background-color: #ddd; + border-bottom: 1px solid #ccc; + + :hover { + background-color: #eee; + } + } + }`; + + + default: + return ''; + } + }} `; \ No newline at end of file diff --git a/src/components/views/Catalog.jsx b/src/components/views/Catalog.jsx index ea854e12..7a9067c0 100644 --- a/src/components/views/Catalog.jsx +++ b/src/components/views/Catalog.jsx @@ -1,16 +1,20 @@ -import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { createPortal } from 'react-dom'; import { Helmet } from 'react-helmet-async'; import InfiniteScroll from 'react-infinite-scroller'; import { Link, useNavigate, useParams } from 'react-router-dom'; +import { confirmAlert } from 'react-confirm-alert'; import { Tooltip } from 'react-tooltip'; -import { useFeed, usePublishComment, useSubplebbit, useSubscribe } from '@plebbit/plebbit-react-hooks'; +import { useAccount, useFeed, usePublishComment, usePublishCommentEdit, useSubplebbit, useSubscribe } from '@plebbit/plebbit-react-hooks'; import { debounce } from 'lodash'; import useGeneralStore from '../../hooks/stores/useGeneralStore'; -import { Container, NavBar, Header, Break, PostForm, PostFormLink, PostFormTable } from '../styled/views/Board.styled'; -import { Threads } from '../styled/views/Catalog.styled'; -import { TopBar, Footer } from '../styled/views/Thread.styled'; +import { Container, NavBar, Header, Break, PostForm, PostFormLink, PostFormTable, PostMenu, BoardForm } from '../styled/views/Board.styled'; +import { Threads, PostMenuCatalog } from '../styled/views/Catalog.styled'; +import { TopBar, Footer, AuthorDeleteAlert } from '../styled/views/Thread.styled'; import CatalogLoader from '../CatalogLoader'; +import EditModal from '../modals/EditModal'; import ImageBanner from '../ImageBanner'; +import ModerationModal from '../modals/ModerationModal'; import OfflineIndicator from '../OfflineIndicator'; import SettingsModal from '../modals/SettingsModal'; import getCommentMediaInfo from '../../utils/getCommentMediaInfo'; @@ -28,8 +32,11 @@ const Catalog = () => { captchaResponse, setCaptchaResponse, setChallengesArray, defaultSubplebbits, + editedComment, setIsCaptchaOpen, + isModerationOpen, setIsModerationOpen, isSettingsOpen, setIsSettingsOpen, + setModeratingCommentCid, setPendingComment, setPendingCommentIndex, setResolveCaptchaPromise, @@ -45,25 +52,56 @@ const Catalog = () => { const subjectRef = useRef(); const commentRef = useRef(); const linkRef = useRef(); + const threadMenuRefs = useRef({}); const navigate = useNavigate(); const [errorMessage, setErrorMessage] = useState(null); - const [successMessage] = useState(null); + const [successMessage, setSuccessMessage] = useState(null); useError(errorMessage, [errorMessage]); useSuccess(successMessage, [successMessage]); const [triggerPublishComment, setTriggerPublishComment] = useState(false); const [prevScrollPos, setPrevScrollPos] = useState(0); const [visible, setVisible] = useState(true); + const [isHoveringOnThread, setIsHoveringOnThread] = useState(false); + const [isModerator, setIsModerator] = useState(false); + const [isEditModalOpen, setIsEditModalOpen] = useState(false); + const [originalCommentContent, setOriginalCommentContent] = useState(null); + const [triggerPublishCommentEdit, setTriggerPublishCommentEdit] = useState(false); + const [deletePost, setDeletePost] = useState(false); + const [isImageSearchOpen, setIsImageSearchOpen] = useState(false); + const [commentCid, setCommentCid] = useState(null); + const [menuPosition, setMenuPosition] = useState({top: 0, left: 0}); + const [openMenuCid, setOpenMenuCid] = useState(null); + const { feed, hasMore, loadMore } = useFeed({subplebbitAddresses: [`${selectedAddress}`], sortType: 'new'}); const { subplebbitAddress } = useParams(); const subplebbit = useSubplebbit({subplebbitAddress: selectedAddress}); + const account = useAccount(); const stateString = useStateString(subplebbit); + useEffect(() => { + if (subplebbit.roles !== undefined) { + const role = subplebbit.roles[account?.author.address]?.role; + + if (role === 'moderator' || role === 'admin' || role === 'owner') { + setIsModerator(true); + } else { + setIsModerator(false); + } + } + }, [account?.author.address, subplebbit.roles]); + + + const handleOptionClick = () => { + setOpenMenuCid(null); + }; + + useEffect(() => { setSelectedAddress(subplebbitAddress); }, [subplebbitAddress, setSelectedAddress]); @@ -85,6 +123,8 @@ const Catalog = () => { setErrorMessage(errorString); } }, [errorString]); + + const { subscribed, subscribe, unsubscribe } = useSubscribe({subplebbitAddress: selectedAddress}); // temporary title from JSON, gets subplebbitAddress from URL @@ -119,8 +159,12 @@ const Catalog = () => { const onChallengeVerification = (challengeVerification) => { if (challengeVerification.challengeSuccess === true) { - navigate(`/p/${selectedAddress}/c/${challengeVerification.publication?.cid}`); - console.log('challenge success'); + if (challengeVerification.publication?.cid !== undefined) { + navigate(`/p/${subplebbitAddress}/c/${challengeVerification.publication?.cid}`); + console.log('challenge success'); + } else { + setSuccessMessage('Challenge Success'); + } } else if (challengeVerification.challengeSuccess === false) { setErrorMessage('Challenge Failed', {reason: challengeVerification.reason, errors: challengeVerification.errors}); @@ -254,6 +298,94 @@ const Catalog = () => { }); }; + + const [publishCommentEditOptions, setPublishCommentEditOptions] = useState({ + commentCid: commentCid, + content: editedComment || undefined, + subplebbitAddress: selectedAddress || subplebbitAddress, + onChallenge, + onChallengeVerification, + onError: (error) => { + setErrorMessage(error); + }, + }); + + + const {error, publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions); + + useEffect(() => { + if (error) { + setErrorMessage(error); + } + }, [error]); + + + const handleAuthorDeleteClick = (commentCid) => { + handleOptionClick(commentCid); + + confirmAlert({ + customUI: ({ onClose }) => { + return ( + +
+

Are you sure you want to delete this post?

+
+ + +
+
+
+ ); + } + }); + }; + + + const handleAuthorEditClick = (comment) => { + handleOptionClick(comment.cid); + setCommentCid(comment.cid); + setOriginalCommentContent(comment.content); + setIsEditModalOpen(true); + } + + + useEffect(() => { + setPublishCommentEditOptions((prevOptions) => ({ + ...prevOptions, + commentCid: commentCid, + content: editedComment || undefined, + })); + }, [commentCid, editedComment]); + + + useEffect(() => { + if (editedComment !== '') { + setTriggerPublishCommentEdit(true); + } + }, [editedComment]); + + + useEffect(() => { + if (publishCommentEditOptions && triggerPublishCommentEdit) { + (async () => { + await publishCommentEdit(); + setTriggerPublishCommentEdit(false); + })(); + } + }, [publishCommentEditOptions, triggerPublishCommentEdit, publishCommentEdit]); + // mobile navbar board select functionality const handleSelectChange = (event) => { const selected = event.target.value; @@ -295,6 +427,16 @@ const Catalog = () => { selectedStyle={selectedStyle} isOpen={isSettingsOpen} closeModal={() => setIsSettingsOpen(false)} /> + setIsModerationOpen(false)} + deletePost={deletePost} /> + setIsEditModalOpen(false)} + originalCommentContent={originalCommentContent} /> <> @@ -484,45 +626,44 @@ const Catalog = () => { const commentMediaInfo = getCommentMediaInfo(thread); const fallbackImgUrl = "assets/filedeleted-res.gif"; return ( - setSelectedThread(thread.cid)}> -
- {commentMediaInfo?.url ? ( - - {commentMediaInfo?.type === "webpage" ? ( - thread.thumbnailUrl ? ( - {commentMediaInfo.type} { - e.target.src = fallbackImgUrl - e.target.onerror = null; - }} /> - ) : null - ) : null} - {commentMediaInfo?.type === "image" ? ( - {commentMediaInfo.type} { - e.target.src = fallbackImgUrl - e.target.onerror = null;}} /> - ) : null} - {commentMediaInfo?.type === "video" ? ( +
{setIsHoveringOnThread(thread.cid)}} + onMouseLeave={() => {setIsHoveringOnThread('')}}> + {commentMediaInfo?.url ? ( + setSelectedThread(thread.cid)}> + {commentMediaInfo?.type === "webpage" ? ( + thread.thumbnailUrl ? ( + {commentMediaInfo.type} { + e.target.src = fallbackImgUrl + e.target.onerror = null; + }} /> + ) : null + ) : null} + {commentMediaInfo?.type === "image" ? ( + {commentMediaInfo.type} { + e.target.src = fallbackImgUrl + e.target.onerror = null;}} /> + ) : null} + {commentMediaInfo?.type === "video" ? (
- )})} ) : ()