diff --git a/src/components/views/Catalog.jsx b/src/components/views/Catalog.jsx
index 029a9dc3..3c6dd1d6 100755
--- a/src/components/views/Catalog.jsx
+++ b/src/components/views/Catalog.jsx
@@ -4,11 +4,11 @@ import { Helmet } from 'react-helmet-async';
import { Link, useNavigate, useParams } from 'react-router-dom';
import { confirmAlert } from 'react-confirm-alert';
import { Tooltip } from 'react-tooltip';
-import { VirtuosoGrid } from 'react-virtuoso';
+import { Virtuoso } from 'react-virtuoso';
import { useAccount, useFeed, usePublishComment, usePublishCommentEdit, useSubplebbit, useSubscribe } from '@plebbit/plebbit-react-hooks';
import { debounce } from 'lodash';
import { Container, NavBar, Header, Break, PostForm, PostFormLink, PostFormTable, PostMenu, BoardForm } from '../styled/views/Board.styled';
-import { Threads, PostMenuCatalog, GridContainer } from '../styled/views/Catalog.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';
@@ -27,9 +27,12 @@ import useError from '../../hooks/useError';
import useStateString from '../../hooks/useStateString';
import useSuccess from '../../hooks/useSuccess';
import useAnonModeStore from '../../hooks/stores/useAnonModeStore';
+import useFeedRows from '../../hooks/useFeedRows';
import useGeneralStore from '../../hooks/stores/useGeneralStore';
+import useWindowWidth from '../../hooks/useWindowWidth';
import packageJson from '../../../package.json'
const {version} = packageJson
+let lastVirtuosoStates = {}
const Catalog = () => {
@@ -65,6 +68,7 @@ const Catalog = () => {
const postMenuRef = useRef(null);
const postMenuCatalogRef = useRef(null);
const selectedThreadCidRef = useRef(null);
+ const virtuosoRef = useRef();
const navigate = useNavigate();
@@ -74,7 +78,6 @@ const Catalog = () => {
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);
@@ -82,29 +85,30 @@ const Catalog = () => {
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 [executeAnonMode, setExecuteAnonMode] = useState(false);
const [isCreateBoardOpen, setIsCreateBoardOpen] = useState(false);
+ useAnonModeRef(selectedThreadCidRef, anonymousMode && executeAnonMode);
+
const account = useAccount();
- const { feed, loadMore } = useFeed({subplebbitAddresses: [`${selectedAddress}`], sortType: 'active'});
+ const { feed, hasMore, loadMore } = useFeed({subplebbitAddresses: [`${selectedAddress}`], sortType: 'active'});
const { subplebbitAddress } = useParams();
const subplebbit = useSubplebbit({subplebbitAddress: selectedAddress});
const stateString = useStateString(subplebbit);
- let feedWithDescriptionAndRules = [];
-
- useAnonModeRef(selectedThreadCidRef, anonymousMode && executeAnonMode);
-
+ let feedWithDescriptionAndRules = [...feed];
if (subplebbit.rules) {
- feedWithDescriptionAndRules.push({type: 'rules', content: subplebbit.rules});
+ feedWithDescriptionAndRules.unshift({ type: 'rules', content: subplebbit.rules });
}
if (subplebbit.description) {
- feedWithDescriptionAndRules.push({type: 'description', content: subplebbit.description});
-}
+ feedWithDescriptionAndRules.unshift({ type: 'description', content: subplebbit.description });
+ }
+ const columnWidth = 180;
+ const windowWidth = useWindowWidth();
+ const columnCount = Math.floor(windowWidth / columnWidth);
+ const rows = useFeedRows(feedWithDescriptionAndRules, columnCount);
useEffect(() => {
if (subplebbit.roles !== undefined) {
@@ -119,30 +123,6 @@ const Catalog = () => {
}, [account?.author.address, subplebbit.roles]);
- 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(() => {
setSelectedAddress(subplebbitAddress);
}, [subplebbitAddress, setSelectedAddress]);
@@ -197,13 +177,6 @@ const Catalog = () => {
}, [prevScrollPos, visible]);
- const tryLoadMore = async () => {
- try {loadMore()}
- catch (e)
- {await new Promise(resolve => setTimeout(resolve, 1000))}
- };
-
-
const onChallengeVerification = (challengeVerification) => {
if (challengeVerification.challengeSuccess === true) {
if (challengeVerification.publication?.cid !== undefined) {
@@ -259,7 +232,7 @@ const Catalog = () => {
useEffect(() => {
if (index !== undefined) {
setPendingCommentIndex(index);
- navigate(`/profile/c/${index}`);
+ navigate(`/profile/c/`);
}
}, [index, navigate, setPendingCommentIndex]);
@@ -292,6 +265,7 @@ const Catalog = () => {
...prevPublishCommentOptions,
author: {
displayName: nameRef.current.value || undefined,
+ address: account?.author.address,
},
title: subjectRef.current.value || undefined,
content: commentRef.current.value || undefined,
@@ -329,7 +303,7 @@ const Catalog = () => {
signer,
author: {
...prevPublishCommentOptions.author,
- address: signer?.address
+ address: signer?.address,
},
};
@@ -409,52 +383,6 @@ const Catalog = () => {
const { publishCommentEdit } = usePublishCommentEdit(publishCommentEditOptions);
- const handleAuthorDeleteClick = (comment) => {
- handleOptionClick(comment.cid);
-
- confirmAlert({
- customUI: ({ onClose }) => {
- return (
-
-
-
Are you sure you want to delete this post?
-
-
-
-
-
-
- );
- }
- });
- };
-
-
- const handleAuthorEditClick = (comment) => {
- handleOptionClick(comment.cid);
- setIsAuthorEdit(true);
- setIsAuthorDelete(false);
- setCommentCid(comment.cid);
- setOriginalCommentContent(comment.content);
- setIsEditModalOpen(true);
- }
-
-
useEffect(() => {
setPublishCommentEditOptions((prevOptions) => ({
...prevOptions,
@@ -511,6 +439,503 @@ const Catalog = () => {
};
+ const CatalogPost = ({post}) => {
+ const thread = post;
+ const commentMediaInfo = getCommentMediaInfo(thread);
+ const linkCount = countLinks(thread);
+ const fallbackImgUrl = "assets/filedeleted-res.gif";
+ const [isHoveringOnThread, setIsHoveringOnThread] = useState(false);
+ const [menuPosition, setMenuPosition] = useState({top: 0, left: 0});
+ const [openMenuCid, setOpenMenuCid] = useState(null);
+
+ const handleOutsideClick = useCallback((e) => {
+ if (openMenuCid !== null && !postMenuRef.current.contains(e.target) && !postMenuCatalogRef.current.contains(e.target)) {
+ setOpenMenuCid(null);
+ }
+ }, [openMenuCid]);
+
+ const handleOptionClick = () => {
+ setOpenMenuCid(null);
+ };
+
+ useEffect(() => {
+ if (openMenuCid !== null) {
+ document.addEventListener('click', handleOutsideClick);
+ } else {
+ document.removeEventListener('click', handleOutsideClick);
+ }
+
+ return () => {
+ document.removeEventListener('click', handleOutsideClick);
+ };
+ }, [openMenuCid, handleOutsideClick]);
+
+ const handleAuthorDeleteClick = (comment) => {
+ handleOptionClick(comment.cid);
+
+ confirmAlert({
+ customUI: ({ onClose }) => {
+ return (
+
+
+
Are you sure you want to delete this post?
+
+
+
+
+
+
+ );
+ }
+ });
+ };
+
+
+ const handleAuthorEditClick = (comment) => {
+ handleOptionClick(comment.cid);
+ setIsAuthorEdit(true);
+ setIsAuthorDelete(false);
+ setCommentCid(comment.cid);
+ setOriginalCommentContent(comment.content);
+ setIsEditModalOpen(true);
+ }
+
+
+ if (post.type === 'rules') {
+ return (
+
setIsHoveringOnThread('rules')}
+ onMouseLeave={() => setIsHoveringOnThread('')}>
+
+
+ R:
0 / L:
0
+
+
+
+
+
{
+ threadMenuRefs.current["rules"] = el;
+ postMenuRef.current = el;
+ }}
+ className='post-menu-button'
+ id='post-menu-button-catalog'
+ 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
+ )}
+
+
+
+ Rules
+ {": " + subplebbit.rules.map((rule, index) => `${index + 1}. ${rule}`).join(' ')}
+
+
+
+ )
+ } else if (post.type === 'description') {
+ return (
+ <>
+
setIsHoveringOnThread('description')}
+ onMouseLeave={() => setIsHoveringOnThread('')}>
+
+ {subplebbit.suggested?.avatarUrl ? (
+

+ ) : null}
+
+ {subplebbit.suggested?.avatarUrl ? (
+
+
+
+
+ ) : null}
+
+
+ R:
0 / L:
0
+ {subplebbit.suggested?.avatarUrl ? null : (
+
+
+
+
+ )}
+
{
+ threadMenuRefs.current["description"] = el;
+ postMenuRef.current = el;
+ }}
+ className='post-menu-button'
+ id='post-menu-button-catalog'
+ rotated={openMenuCid === "description"}
+ onClick={(event) => {
+ event.stopPropagation();
+ const rect = threadMenuRefs.current["description"].getBoundingClientRect();
+ setMenuPosition({top: rect.top + window.scrollY, left: rect.left});
+ setOpenMenuCid(prevCid => (prevCid === "description" ? null : "description"));
+ }}
+ >
+ ▶
+
+
+ {createPortal(
+ {postMenuCatalogRef.current = el}}
+ onClick={(event) => event.stopPropagation()}
+ style={{position: "absolute",
+ top: menuPosition.top + 7,
+ left: menuPosition.left}}>
+
+
+ - handleOptionClick("description")}>Hide thread
+ {/* {isModerator ? (
+ <>
+ change description
+ >
+ ) : null} */}
+ {subplebbit.suggested?.avatarUrl ? (
+ - {setIsImageSearchOpen(true)}}
+ onMouseLeave={() => {setIsImageSearchOpen(false)}}>
+ Image search »
+
+ - handleOptionClick("description")}>
+ Google
+
+ - handleOptionClick("description")}>
+ Yandex
+
+ - handleOptionClick("description")}>
+ SauceNAO
+
+
+
+ ) : null}
+
+
+ , document.body
+ )}
+
+
setSelectedThread("description")}>
+
+ Welcome to {subplebbit.title || subplebbit.address}
+ {": " + subplebbit.description}
+
+
+
+ >
+ )
+ } else {
+ return (
+
{setIsHoveringOnThread(thread.cid)}}
+ onMouseLeave={() => {setIsHoveringOnThread('')}}>
+ {commentMediaInfo?.url ? (
+
setSelectedThread(thread.cid)}>
+ {commentMediaInfo?.type === "webpage" ? (
+ thread.thumbnailUrl ? (
+

{
+ e.target.src = fallbackImgUrl
+ e.target.onerror = null;
+ }} />
+ ) : null
+ ) : null}
+ {commentMediaInfo?.type === "image" ? (
+

{
+ e.target.src = fallbackImgUrl
+ e.target.onerror = null;}} />
+ ) : null}
+ {commentMediaInfo?.type === "video" ? (
+
+ )
+ }
+ };
+
+
+ const CatalogRow = ({row}) => {
+ const posts = []
+ for (const post of row) {
+ posts.push(
)
+ }
+ return
{posts}
+ }
+
+ useEffect(() => {
+ const setLastVirtuosoState = () => {
+ virtuosoRef.current?.getState((snapshot) => {
+ if (snapshot?.scrollTop === 0 || snapshot?.ranges?.length) {
+ lastVirtuosoStates[`${selectedAddress}-catalog`] = snapshot;
+ }
+ });
+ };
+ window.addEventListener('scroll', setLastVirtuosoState);
+
+ return () => window.removeEventListener('scroll', setLastVirtuosoState);
+ }, [selectedAddress]);
+
+
+ const lastVirtuosoState = lastVirtuosoStates[`${selectedAddress}-catalog`];
+
+
return (
<>
@@ -726,413 +1151,18 @@ const Catalog = () => {
{subplebbit?.state !== "failed" && subplebbit.state === "succeeded" ? (
- {
- const thread = feed[index];
- const commentMediaInfo = getCommentMediaInfo(thread);
- const linkCount = countLinks(thread);
- const fallbackImgUrl = "assets/filedeleted-res.gif";
- const item = feedWithDescriptionAndRules[index];
-
- if (item?.type === 'rules') {
- return (
- setIsHoveringOnThread('rules')}
- onMouseLeave={() => setIsHoveringOnThread('')}>
-
-
- R:
0 / L:
0
-
-
-
-
-
{
- threadMenuRefs.current["rules"] = el;
- postMenuRef.current = el;
- }}
- className='post-menu-button'
- id='post-menu-button-catalog'
- 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
- )}
-
-
-
- Rules
- {": " + subplebbit.rules.map((rule, index) => `${index + 1}. ${rule}`).join(' ')}
-
-
-
- )
- } else if (item?.type === 'description') {
- return (
- <>
- setIsHoveringOnThread('description')}
- onMouseLeave={() => setIsHoveringOnThread('')}>
-
- {subplebbit.suggested?.avatarUrl ? (
-

- ) : null}
-
- {subplebbit.suggested?.avatarUrl ? (
-
-
-
-
- ) : null}
-
-
- R:
0 / L:
0
- {subplebbit.suggested?.avatarUrl ? null : (
-
-
-
-
- )}
-
{
- threadMenuRefs.current["description"] = el;
- postMenuRef.current = el;
- }}
- className='post-menu-button'
- id='post-menu-button-catalog'
- rotated={openMenuCid === "description"}
- onClick={(event) => {
- event.stopPropagation();
- const rect = threadMenuRefs.current["description"].getBoundingClientRect();
- setMenuPosition({top: rect.top + window.scrollY, left: rect.left});
- setOpenMenuCid(prevCid => (prevCid === "description" ? null : "description"));
- }}
- >
- ▶
-
-
- {createPortal(
- {postMenuCatalogRef.current = el}}
- onClick={(event) => event.stopPropagation()}
- style={{position: "absolute",
- top: menuPosition.top + 7,
- left: menuPosition.left}}>
-
-
- - handleOptionClick("description")}>Hide thread
- {/* {isModerator ? (
- <>
- change description
- >
- ) : null} */}
- {subplebbit.suggested?.avatarUrl ? (
- - {setIsImageSearchOpen(true)}}
- onMouseLeave={() => {setIsImageSearchOpen(false)}}>
- Image search »
-
- - handleOptionClick("description")}>
- Google
-
- - handleOptionClick("description")}>
- Yandex
-
- - handleOptionClick("description")}>
- SauceNAO
-
-
-
- ) : null}
-
-
- , document.body
- )}
-
-
setSelectedThread("description")}>
-
- Welcome to {subplebbit.title || subplebbit.address}
- {": " + subplebbit.description}
-
-
-
- >
- )
- } else {
- return (
- {setIsHoveringOnThread(thread.cid)}}
- onMouseLeave={() => {setIsHoveringOnThread('')}}>
- {commentMediaInfo?.url ? (
-
setSelectedThread(thread.cid)}>
- {commentMediaInfo?.type === "webpage" ? (
- thread.thumbnailUrl ? (
-

{
- e.target.src = fallbackImgUrl
- e.target.onerror = null;
- }} />
- ) : null
- ) : null}
- {commentMediaInfo?.type === "image" ? (
-

{
- e.target.src = fallbackImgUrl
- e.target.onerror = null;}} />
- ) : null}
- {commentMediaInfo?.type === "video" ? (
-
- )
- }
- }}
- endReached={tryLoadMore}
+ totalCount={rows?.length || 0}
+ data={rows}
+ style={{maxWidth: '100%'}}
+ itemContent={(index, row) => }
useWindowScroll={true}
- components={{List: GridContainer}}
+ components={{ Footer: () => hasMore ? : null}}
+ endReached={loadMore}
+ ref={virtuosoRef}
+ restoreStateFrom={lastVirtuosoState}
+ initialScrollTop={lastVirtuosoState?.scrollTop}
/>
) : ()}
diff --git a/src/components/views/Subscriptions.jsx b/src/components/views/Subscriptions.jsx
index 7993a2f0..7e380338 100755
--- a/src/components/views/Subscriptions.jsx
+++ b/src/components/views/Subscriptions.jsx
@@ -1375,6 +1375,17 @@ const Subscriptions = () => {
replyMediaInfo?.url.slice(0, 30) + "(...)" :
replyMediaInfo?.url
} ({replyMediaInfo?.type})
+ {replyMediaInfo?.type === "video" || "iframe" ? (
+ isReplyThumbnailClicked[index] ? (
+
+ -[
+ {handleThumbnailClick(index, 'reply')}}>Close
+ ]
+ )
+ : null)
+ : null}
{replyMediaInfo?.type === "webpage" ? (
@@ -1401,12 +1412,29 @@ const Subscriptions = () => {
) : null}
{replyMediaInfo?.type === "video" ? (
+