mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
+12
-10
@@ -22,7 +22,7 @@ const BoardLayout = () => {
|
||||
|
||||
const isValidAccountCommentIndex = !accountCommentIndex || (!isNaN(parseInt(accountCommentIndex)) && parseInt(accountCommentIndex) >= 0);
|
||||
const isValidCommentCid = !commentCid || /^Qm[a-zA-Z0-9]{44}$/.test(commentCid);
|
||||
const isValidSubplebbitAddress = !subplebbitAddress || subplebbitAddress.includes('.') || /^12D3K[a-zA-Z0-9]{44}$/.test(subplebbitAddress);
|
||||
const isValidSubplebbitAddress = !subplebbitAddress || subplebbitAddress.includes('.') || /^12D3KooW[a-zA-Z0-9]{44}$/.test(subplebbitAddress);
|
||||
|
||||
if (!isValidAccountCommentIndex || !isValidCommentCid || !isValidSubplebbitAddress) {
|
||||
return <NotFound />;
|
||||
@@ -70,17 +70,8 @@ const App = () => {
|
||||
<Route element={<BoardLayout />}>
|
||||
<Route path='/p/:subplebbitAddress' element={<Board />} />
|
||||
<Route path='/p/:subplebbitAddress/settings' element={<Board />} />
|
||||
<Route path='/p/all' element={<Board />} />
|
||||
<Route path='/p/all/settings' element={<Board />} />
|
||||
<Route path='/p/subscriptions' element={<Board />} />
|
||||
<Route path='/p/subscriptions/settings' element={<Board />} />
|
||||
|
||||
<Route path='/p/:subplebbitAddress/catalog' element={<Catalog />} />
|
||||
<Route path='/p/:subplebbitAddress/catalog/settings' element={<Catalog />} />
|
||||
<Route path='/p/all/catalog' element={<Catalog />} />
|
||||
<Route path='/p/all/catalog/settings' element={<Catalog />} />
|
||||
<Route path='/p/subscriptions/catalog' element={<Catalog />} />
|
||||
<Route path='/p/subscriptions/catalog/settings' element={<Catalog />} />
|
||||
|
||||
<Route path='/p/:subplebbitAddress/c/:commentCid' element={<PostPage />} />
|
||||
<Route path='/p/:subplebbitAddress/c/:commentCid/settings' element={<PostPage />} />
|
||||
@@ -89,6 +80,17 @@ const App = () => {
|
||||
<Route path='/p/:subplebbitAddress/rules' element={<PostPage />} />
|
||||
<Route path='/p/:subplebbitAddress/rules/settings' element={<PostPage />} />
|
||||
|
||||
<Route path='/p/all' element={<Board />} />
|
||||
<Route path='/p/all/description' element={<PostPage />} />
|
||||
<Route path='/p/all/settings' element={<Board />} />
|
||||
<Route path='/p/all/catalog' element={<Catalog />} />
|
||||
<Route path='/p/all/catalog/settings' element={<Catalog />} />
|
||||
|
||||
<Route path='/p/subscriptions' element={<Board />} />
|
||||
<Route path='/p/subscriptions/settings' element={<Board />} />
|
||||
<Route path='/p/subscriptions/catalog' element={<Catalog />} />
|
||||
<Route path='/p/subscriptions/catalog/settings' element={<Catalog />} />
|
||||
|
||||
<Route path='/profile/:accountCommentIndex' element={<PendingPost />} />
|
||||
<Route path='/profile/:accountCommentIndex/settings' element={<PendingPost />} />
|
||||
</Route>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
|
||||
import { Link, useLocation, useParams } from 'react-router-dom';
|
||||
import { useAccountComment, useSubscribe } from '@plebbit/plebbit-react-hooks';
|
||||
import styles from './board-buttons.module.css';
|
||||
import { isAllView, isCatalogView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||
@@ -16,12 +16,18 @@ const OptionsButton = () => {
|
||||
return <button className='button'>{t('options')}</button>;
|
||||
};
|
||||
|
||||
const CatalogButton = ({ address, isInAllView, isInCatalogView, isInSubscriptionsView }: BoardButtonsProps) => {
|
||||
const CatalogButton = ({ address, isInAllView, isInCatalogView }: BoardButtonsProps) => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const link = isInAllView ? `/p/all/catalog` : isInSubscriptionsView ? `/p/subscriptions/catalog` : isInCatalogView ? `/p/${address}` : `/p/${address}/catalog`;
|
||||
|
||||
return <button className='button'>{isInCatalogView ? <span onClick={() => navigate(-1)}>{t('return')}</span> : <Link to={link}>{t('catalog')}</Link>}</button>;
|
||||
return (
|
||||
<button className='button'>
|
||||
{isInCatalogView ? (
|
||||
<Link to={isInAllView ? '/p/all' : `/p/${address}`}>{t('return')}</Link>
|
||||
) : (
|
||||
<Link to={isInAllView ? `/p/all/catalog` : `/p/${address}/catalog`}>{t('catalog')}</Link>
|
||||
)}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
const SubscribeButton = ({ address }: BoardButtonsProps) => {
|
||||
@@ -56,18 +62,21 @@ const BottomButton = () => {
|
||||
export const MobileBoardButtons = () => {
|
||||
const params = useParams();
|
||||
const location = useLocation();
|
||||
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
|
||||
const subplebbitAddress = params?.subplebbitAddress || accountComment?.subplebbitAddress;
|
||||
const isInPostView = isPostPageView(location.pathname, params);
|
||||
const isInAllView = isAllView(location.pathname);
|
||||
const isInCatalogView = isCatalogView(location.pathname);
|
||||
const isInPendingPostPage = isPendingPostView(location.pathname, params);
|
||||
const isInPostView = isPostPageView(location.pathname, params);
|
||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname);
|
||||
|
||||
const accountComment = useAccountComment({ commentIndex: params?.accountCommentIndex as any });
|
||||
const subplebbitAddress = params?.subplebbitAddress || accountComment?.subplebbitAddress;
|
||||
|
||||
return (
|
||||
<div className={styles.mobileBoardButtons}>
|
||||
{isInPostView || isInPendingPostPage ? (
|
||||
<div className={styles.mobilePostPageButtons}>
|
||||
<ReturnButton address={subplebbitAddress} />
|
||||
<CatalogButton address={subplebbitAddress} />
|
||||
<CatalogButton address={subplebbitAddress} isInAllView={isInAllView} />
|
||||
<BottomButton />
|
||||
<div className={styles.mobilePostPageButtonsSecondRow}>
|
||||
<OptionsButton />
|
||||
@@ -77,7 +86,7 @@ export const MobileBoardButtons = () => {
|
||||
) : (
|
||||
<>
|
||||
<OptionsButton />
|
||||
<CatalogButton address={subplebbitAddress} isInCatalogView={isInCatalogView} />
|
||||
<CatalogButton address={subplebbitAddress} isInAllView={isInAllView} isInCatalogView={isInCatalogView} isInSubscriptionsView={isInSubscriptionsView} />
|
||||
<SubscribeButton address={subplebbitAddress} />
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
font-size: var(--boardnav-mobile-font-size);
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
z-index: 6;
|
||||
z-index: 3;
|
||||
animation-name: stickyMenuAnimation;
|
||||
animation-duration: 1s;
|
||||
animation-timing-function: linear;
|
||||
|
||||
@@ -8,7 +8,6 @@ import useDefaultSubplebbits, { categorizeSubplebbits, useDefaultSubplebbitAddre
|
||||
const BoardNavDesktop = () => {
|
||||
const { t } = useTranslation();
|
||||
const location = useLocation();
|
||||
const params = useParams();
|
||||
const isInCatalogView = isCatalogView(location.pathname);
|
||||
const subplebbits = useDefaultSubplebbits();
|
||||
|
||||
@@ -47,7 +46,6 @@ const BoardNavMobile = ({ subplebbitAddress }: { subplebbitAddress: string }) =>
|
||||
const currentSubplebbitIsInList = subplebbitAddresses.some((address: string) => address === subplebbitAddress);
|
||||
|
||||
const location = useLocation();
|
||||
const params = useParams();
|
||||
const isInAllView = isAllView(location.pathname);
|
||||
const isInCatalogView = isCatalogView(location.pathname);
|
||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname);
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Comment } from '@plebbit/plebbit-react-hooks';
|
||||
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
||||
import { isAllView } from '../../lib/utils/view-utils';
|
||||
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
|
||||
import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
|
||||
import styles from './catalog-row.module.css';
|
||||
@@ -60,7 +61,11 @@ const CatalogPost = ({ openMenu, post, toggleMenu }: CatalogPostProps) => {
|
||||
}
|
||||
|
||||
const thumbnailDimensions = { '--width': displayWidth, '--height': displayHeight } as React.CSSProperties;
|
||||
const postLink = `/p/${subplebbitAddress}/${isDescription ? 'description' : isRules ? 'rules' : `c/${cid}`}`;
|
||||
|
||||
const location = useLocation();
|
||||
const isInAllView = isAllView(location.pathname);
|
||||
|
||||
const postLink = isInAllView && isDescription ? `/p/all/description` : `/p/${subplebbitAddress}/${isDescription ? 'description' : isRules ? 'rules' : `c/${cid}`}`;
|
||||
|
||||
const linkCount = useCountLinksInReplies(post);
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
font-size: 10pt;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
z-index: 1000;
|
||||
z-index: 999;
|
||||
background-color: var(--challenge-modal-background-color);
|
||||
border: var(--challenge-modal-border);
|
||||
}
|
||||
|
||||
@@ -5,9 +5,17 @@
|
||||
max-width: 250px;
|
||||
max-height: 250px;
|
||||
margin: 3px 20px 5px 0;
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.subplebbitAvatar {
|
||||
max-width: 250px;
|
||||
max-height: 250px;
|
||||
margin: 3px 20px 5px 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.thumbnailSmall {
|
||||
@@ -17,9 +25,10 @@
|
||||
max-width: 125px;
|
||||
max-height: 125px;
|
||||
margin: 3px 10px 5px 5px;
|
||||
display: flex;
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.thumbnailSmall span, .thumbnailSmall a {
|
||||
@@ -43,7 +52,6 @@
|
||||
}
|
||||
|
||||
.content img, .content video, .content iframe, .content audio {
|
||||
box-sizing: border-box;
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
@@ -57,11 +57,6 @@ const Thumbnail = ({ commentMediaInfo, isOutOfFeed, isReply, linkHeight, linkWid
|
||||
displayHeight = '100%';
|
||||
}
|
||||
|
||||
if (isOutOfFeed) {
|
||||
displayWidth = 'unset';
|
||||
displayHeight = 'unset';
|
||||
}
|
||||
|
||||
let thumbnailComponent: React.ReactNode = null;
|
||||
const iframeThumbnail = patternThumbnailUrl || thumbnail;
|
||||
const gifFrameUrl = useFetchGifFirstFrame(type === 'gif' ? url : undefined);
|
||||
@@ -86,7 +81,9 @@ const Thumbnail = ({ commentMediaInfo, isOutOfFeed, isReply, linkHeight, linkWid
|
||||
|
||||
const linkWithoutThumbnail = url && new URL(url);
|
||||
|
||||
return isMobile || isReply ? (
|
||||
return isOutOfFeed ? (
|
||||
<span className={styles.subplebbitAvatar}>{thumbnailComponent}</span>
|
||||
) : isMobile || isReply ? (
|
||||
<ThumbnailSmall style={thumbnailDimensions} thumbnailSmallPadding={thumbnailSmallPadding}>
|
||||
{thumbnailComponent}
|
||||
{isMobile &&
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { useState } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { Link } from 'react-router-dom';
|
||||
@@ -6,6 +6,7 @@ import { useAccount } from '@plebbit/plebbit-react-hooks';
|
||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||
import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../../lib/utils/media-utils';
|
||||
import { getFormattedDate } from '../../../lib/utils/time-utils';
|
||||
import { isValidURL } from '../../../lib/utils/url-utils';
|
||||
import { isAllView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../../lib/utils/view-utils';
|
||||
import useCountLinksInReplies from '../../../hooks/use-count-links-in-replies';
|
||||
import useReplies from '../../../hooks/use-replies';
|
||||
@@ -16,6 +17,7 @@ import LoadingEllipsis from '../../loading-ellipsis';
|
||||
import Markdown from '../../markdown';
|
||||
import { PostProps } from '../post';
|
||||
import styles from '../post.module.css';
|
||||
import PostMenu from '../post-menu';
|
||||
import _ from 'lodash';
|
||||
|
||||
const PostInfo = ({ openReplyModal, post, roles }: PostProps) => {
|
||||
@@ -40,30 +42,6 @@ const PostInfo = ({ openReplyModal, post, roles }: PostProps) => {
|
||||
const account = useAccount();
|
||||
const accountShortAddress = account?.author?.shortAddress;
|
||||
|
||||
const [menuBtnRotated, setMenuBtnRotated] = useState(false);
|
||||
const menuRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const handleMenuButtonClick = () => {
|
||||
setMenuBtnRotated((prev) => !prev);
|
||||
};
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (menuRef.current && !menuRef.current.contains(event.target as Node)) {
|
||||
setMenuBtnRotated(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (menuBtnRotated) {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
} else {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
}
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, [menuBtnRotated]);
|
||||
|
||||
return (
|
||||
<div className={styles.postInfo}>
|
||||
<span className={styles.checkbox}>
|
||||
@@ -82,6 +60,12 @@ const PostInfo = ({ openReplyModal, post, roles }: PostProps) => {
|
||||
{isDescription || isRules ? '' : ' '}
|
||||
</span>
|
||||
<span className={styles.postNum}>
|
||||
{subplebbitAddress && (isInAllView || isInSubscriptionsView) && !isReply && (
|
||||
<span className={styles.postNumLink}>
|
||||
{' '}
|
||||
<Link to={`/p/${subplebbitAddress}`}>p/{subplebbitAddress && Plebbit.getShortAddress(subplebbitAddress)}</Link>{' '}
|
||||
</span>
|
||||
)}
|
||||
{!(isDescription || isRules) && (
|
||||
<span className={styles.postNumLink}>
|
||||
<Link to={`/p/${subplebbitAddress}/c/${cid}`} className={styles.linkToPost} title={t('link_to_post')} onClick={(e) => !cid && e.preventDefault()}>
|
||||
@@ -96,12 +80,6 @@ const PostInfo = ({ openReplyModal, post, roles }: PostProps) => {
|
||||
)}
|
||||
</span>
|
||||
)}
|
||||
{subplebbitAddress && (isInAllView || isInSubscriptionsView) && (
|
||||
<span className={styles.postNumLink}>
|
||||
{' '}
|
||||
<Link to={`/p/${subplebbitAddress}`}>p/{subplebbitAddress && Plebbit.getShortAddress(subplebbitAddress)}</Link>
|
||||
</span>
|
||||
)}
|
||||
{pinned && (
|
||||
<span className={`${styles.stickyIconWrapper} ${!locked && styles.addPaddingBeforeReply}`}>
|
||||
<img src='assets/icons/sticky.gif' alt='' className={styles.stickyIcon} title={t('sticky')} />
|
||||
@@ -114,15 +92,17 @@ const PostInfo = ({ openReplyModal, post, roles }: PostProps) => {
|
||||
)}
|
||||
{!isInPostView && !isReply && (
|
||||
<span className={styles.replyButton}>
|
||||
[<Link to={`/p/${subplebbitAddress}/${isDescription ? 'description' : isRules ? 'rules' : `c/${postCid}`}`}>{_.capitalize(t('reply'))}</Link>]
|
||||
[
|
||||
<Link
|
||||
to={isInAllView && isDescription ? '/p/all/description' : `/p/${subplebbitAddress}/${isDescription ? 'description' : isRules ? 'rules' : `c/${postCid}`}`}
|
||||
>
|
||||
{_.capitalize(t('reply'))}
|
||||
</Link>
|
||||
]
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<span className={styles.postMenuBtnWrapper} ref={menuRef}>
|
||||
<span className={styles.postMenuBtn} title='Post menu' onClick={handleMenuButtonClick} style={{ transform: menuBtnRotated ? 'rotate(90deg)' : 'rotate(0deg)' }}>
|
||||
▶
|
||||
</span>
|
||||
</span>
|
||||
<PostMenu post={post} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -253,7 +233,7 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies }: PostProps)
|
||||
<span className={`${styles.hideButton} ${styles.hideThread}`} />
|
||||
</span>
|
||||
)}
|
||||
{link && <PostMedia post={post} />}
|
||||
{link && isValidURL(link) && <PostMedia post={post} />}
|
||||
<PostInfo openReplyModal={openReplyModal} post={post} roles={roles} />
|
||||
{!content && <div className={styles.spacer} />}
|
||||
{content && <PostMessage post={post} />}
|
||||
@@ -285,7 +265,7 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies }: PostProps)
|
||||
<div className={styles.sideArrows}>{'>>'}</div>
|
||||
<div className={styles.reply}>
|
||||
<PostInfo openReplyModal={openReplyModal} post={reply} roles={roles} />
|
||||
{reply.link && <PostMedia post={reply} />}
|
||||
{reply.link && isValidURL(reply.link) && <PostMedia post={reply} />}
|
||||
{reply.content && <PostMessage post={reply} />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './post-menu';
|
||||
@@ -0,0 +1,86 @@
|
||||
.postMenu {
|
||||
position: absolute;
|
||||
background-color: var(--post-menu-background-color);
|
||||
border-bottom: var(--post-menu-border-bottom);
|
||||
border-right: var(--post-menu-border-right);
|
||||
outline: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.postMenuBtnWrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.postMenuBtn {
|
||||
color: var(--post-menu-button-color);
|
||||
transition: transform 0.1s;
|
||||
transform: rotate(90deg);
|
||||
position: absolute;
|
||||
bottom: -4px;
|
||||
}
|
||||
|
||||
.postMenuBtn:hover {
|
||||
cursor: pointer;
|
||||
color: var(--post-menu-button-color-hover);
|
||||
}
|
||||
|
||||
.postMenuItem {
|
||||
cursor: pointer;
|
||||
padding: 2px 4px;
|
||||
border: 1px solid var(--post-menu-item-border-color);
|
||||
background-color: var(--post-menu-item-background-color);
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.postMenuItem:hover {
|
||||
background-color: var(--post-menu-item-hover-background-color);
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.dropdownMenu {
|
||||
position: absolute;
|
||||
left: 100%;
|
||||
top: -1px;
|
||||
display: none;
|
||||
border-bottom: var(--post-menu-border-bottom);
|
||||
border-right: var(--post-menu-border-right);
|
||||
}
|
||||
|
||||
.dropdown a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.dropdown:hover .dropdownMenu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.postMenuBtn {
|
||||
width: 1em;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.postMenuBtnWrapper {
|
||||
position: relative;
|
||||
padding-right: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 640px) {
|
||||
.postMenuBtn {
|
||||
right: -2px;
|
||||
opacity: var(--post-menu-btn-opacity);
|
||||
}
|
||||
|
||||
.stickyIconWrapper {
|
||||
padding-right: 18px;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
import { useState } from 'react';
|
||||
import { autoUpdate, flip, FloatingFocusManager, offset, shift, useClick, useDismiss, useFloating, useId, useInteractions, useRole } from '@floating-ui/react';
|
||||
import { Comment } from '@plebbit/plebbit-react-hooks';
|
||||
import styles from './post-menu.module.css';
|
||||
import { getCommentMediaInfo } from '../../../lib/utils/media-utils';
|
||||
import { copyShareLinkToClipboard, isValidURL } from '../../../lib/utils/url-utils';
|
||||
|
||||
interface PostMenuProps {
|
||||
cid: string;
|
||||
isDescription?: boolean;
|
||||
isRules?: boolean;
|
||||
subplebbitAddress: string;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const CopyLinkButton = ({ cid, subplebbitAddress, onClose }: PostMenuProps) => {
|
||||
return (
|
||||
<div
|
||||
onClick={() => {
|
||||
copyShareLinkToClipboard(subplebbitAddress, cid);
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
<div className={styles.postMenuItem}>Copy link</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ImageSearchButton = ({ url, onClose }: { url: string; onClose: () => void }) => {
|
||||
const [isImageSearchMenuOpen, setIsImageSearchMenuOpen] = useState(false);
|
||||
return (
|
||||
<div
|
||||
className={`${styles.postMenuItem} ${styles.dropdown}`}
|
||||
onMouseOver={() => setIsImageSearchMenuOpen(true)}
|
||||
onMouseLeave={() => setIsImageSearchMenuOpen(false)}
|
||||
onClick={onClose}
|
||||
>
|
||||
Image search »
|
||||
{isImageSearchMenuOpen && (
|
||||
<div className={styles.dropdownMenu}>
|
||||
<a href={`https://lens.google.com/uploadbyurl?url=${url}`} target='_blank' rel='noreferrer'>
|
||||
<div className={styles.postMenuItem}>Google</div>
|
||||
</a>
|
||||
<a href={`https://www.yandex.com/images/search?img_url=${url}&rpt=imageview`} target='_blank' rel='noreferrer'>
|
||||
<div className={styles.postMenuItem}>Yandex</div>
|
||||
</a>
|
||||
<a href={`https://saucenao.com/search.php?url=${url}`} target='_blank' rel='noreferrer'>
|
||||
<div className={styles.postMenuItem}>SauceNAO</div>
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ViewOnButton = ({ cid, isDescription, isRules, subplebbitAddress, onClose }: PostMenuProps) => {
|
||||
const [isClientRedirectMenuOpen, setIsClientRedirectMenuOpen] = useState(false);
|
||||
const viewOnOtherClientLink = `p/${subplebbitAddress}${isDescription || isRules ? '' : `/c/${cid}`}`;
|
||||
return (
|
||||
<div
|
||||
className={`${styles.postMenuItem} ${styles.dropdown}`}
|
||||
onMouseOver={() => setIsClientRedirectMenuOpen(true)}
|
||||
onMouseLeave={() => setIsClientRedirectMenuOpen(false)}
|
||||
onClick={onClose}
|
||||
>
|
||||
View on »
|
||||
{isClientRedirectMenuOpen && (
|
||||
<div className={styles.dropdownMenu}>
|
||||
<a href={`https://seedit.app/#/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'>
|
||||
<div className={styles.postMenuItem}>Seedit</div>
|
||||
</a>
|
||||
<a href={`https://plebones.netlify.app/#/${viewOnOtherClientLink}`} target='_blank' rel='noreferrer'>
|
||||
<div className={styles.postMenuItem}>Plebones</div>
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const PostMenu = ({ post }: { post: Comment }) => {
|
||||
const { cid, isDescription, isRules, link, subplebbitAddress } = post;
|
||||
const commentMediaInfo = getCommentMediaInfo(post);
|
||||
const { thumbnail, type, url } = commentMediaInfo || {};
|
||||
const [menuBtnRotated, setMenuBtnRotated] = useState(false);
|
||||
|
||||
const { refs, floatingStyles, context } = useFloating({
|
||||
placement: 'bottom-start',
|
||||
open: menuBtnRotated,
|
||||
onOpenChange: setMenuBtnRotated,
|
||||
middleware: [offset({ mainAxis: 10, crossAxis: 5 }), flip({ fallbackAxisSideDirection: 'end' }), shift({ padding: 10 })],
|
||||
whileElementsMounted: autoUpdate,
|
||||
});
|
||||
const click = useClick(context);
|
||||
const dismiss = useDismiss(context);
|
||||
const role = useRole(context);
|
||||
const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss, role]);
|
||||
const headingId = useId();
|
||||
|
||||
const handleClose = () => setMenuBtnRotated(false);
|
||||
|
||||
return (
|
||||
<>
|
||||
<span className={styles.postMenuBtnWrapper} ref={refs.setReference} {...getReferenceProps()}>
|
||||
<span
|
||||
className={styles.postMenuBtn}
|
||||
title='Post menu'
|
||||
onClick={() => setMenuBtnRotated((prev) => !prev)}
|
||||
style={{ transform: menuBtnRotated ? 'rotate(90deg)' : 'rotate(0deg)' }}
|
||||
>
|
||||
▶
|
||||
</span>
|
||||
</span>
|
||||
{menuBtnRotated && (
|
||||
<FloatingFocusManager context={context} modal={false}>
|
||||
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
|
||||
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} onClose={handleClose} />}
|
||||
{link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButton url={url} onClose={handleClose} />}
|
||||
<ViewOnButton cid={cid} isDescription={isDescription} isRules={isRules} subplebbitAddress={subplebbitAddress} onClose={handleClose} />
|
||||
</div>
|
||||
</FloatingFocusManager>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PostMenu;
|
||||
@@ -72,7 +72,7 @@ const PostInfoAndMedia = ({ openReplyModal, post, roles }: PostProps) => {
|
||||
)}
|
||||
</span>
|
||||
<span className={styles.dateTimePostNum}>
|
||||
{subplebbitAddress && (isInAllView || isInSubscriptionsView) && (
|
||||
{subplebbitAddress && (isInAllView || isInSubscriptionsView) && !isReply && (
|
||||
<div className={styles.postNumLink}>
|
||||
{' '}
|
||||
<Link to={`/p/${subplebbitAddress}`}>p/{subplebbitAddress && Plebbit.getShortAddress(subplebbitAddress)}</Link>
|
||||
@@ -164,6 +164,7 @@ const PostMobile = ({ openReplyModal, post, roles, showAllReplies }: PostProps)
|
||||
|
||||
const params = useParams();
|
||||
const location = useLocation();
|
||||
const isInAllView = isAllView(location.pathname);
|
||||
const isInPendingPostView = isPendingPostView(location.pathname, params);
|
||||
const isInPostView = isPostPageView(location.pathname, params);
|
||||
|
||||
@@ -188,7 +189,10 @@ const PostMobile = ({ openReplyModal, post, roles, showAllReplies }: PostProps)
|
||||
{replyCount > 0 && `${replyCount} Replies`}
|
||||
{linksCount > 0 && ` / ${linksCount} Links`}
|
||||
</span>
|
||||
<Link to={`/p/${subplebbitAddress}/${isDescription ? 'description' : isRules ? 'rules' : `c/${cid}`}`} className='button'>
|
||||
<Link
|
||||
to={isInAllView && isDescription ? '/p/all/description' : `/p/${subplebbitAddress}/${isDescription ? 'description' : isRules ? 'rules' : `c/${cid}`}`}
|
||||
className='button'
|
||||
>
|
||||
{t('view_thread')}
|
||||
</Link>
|
||||
</div>
|
||||
@@ -205,7 +209,7 @@ const PostMobile = ({ openReplyModal, post, roles, showAllReplies }: PostProps)
|
||||
<div className={styles.reply}>
|
||||
<div className={styles.replyContainer}>
|
||||
<PostInfoAndMedia openReplyModal={openReplyModal} post={reply} roles={roles} />
|
||||
{content && <PostMessageMobile post={reply} />}
|
||||
{reply.content && <PostMessageMobile post={reply} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -131,37 +131,6 @@
|
||||
color: var(--button-desktop-text-color-hover);
|
||||
}
|
||||
|
||||
.postMenuBtnWrapper {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.replyDesktop .postMenuBtnWrapper {
|
||||
padding-right: 12px;
|
||||
}
|
||||
|
||||
.replyDesktop .stickyIconWrapper {
|
||||
padding-right: 18px;
|
||||
}
|
||||
|
||||
.postDesktop .postMenuBtn, .replyDesktop .postMenuBtn {
|
||||
color: var(--post-menu-button-color);
|
||||
transition: transform 0.1s;
|
||||
position: absolute;
|
||||
opacity: var(--post-menu-btn-opacity);
|
||||
bottom: -4px;
|
||||
}
|
||||
|
||||
.replyDesktop .postMenuBtn {
|
||||
right: -2px;
|
||||
}
|
||||
|
||||
.postDesktop .postMenuBtn:hover, .replyDesktop .postMenuBtn:hover {
|
||||
cursor: pointer;
|
||||
color: var(--post-menu-button-color-hover);
|
||||
}
|
||||
|
||||
.postDesktop .spacer {
|
||||
width: 100%;
|
||||
padding: 5px;
|
||||
@@ -260,20 +229,6 @@
|
||||
clear: left;
|
||||
}
|
||||
|
||||
.postMobile .postMenuBtn {
|
||||
color: var(--post-mobile-menu-button-color);
|
||||
line-height: 1em;
|
||||
width: 1em;
|
||||
text-align: center;
|
||||
transition: transform 0.1s;
|
||||
transform: rotate(90deg);
|
||||
-webkit-transform: rotate(90deg);
|
||||
float: left;
|
||||
font-weight: 700;
|
||||
font-size: 16px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.postMobile .nameBlock {
|
||||
clear: none;
|
||||
}
|
||||
@@ -408,10 +363,6 @@
|
||||
color: var(--post-mobile-info-text-color);
|
||||
}
|
||||
|
||||
.replyMobile .postInfo .postMenuBtn {
|
||||
width: 1em;
|
||||
}
|
||||
|
||||
.replyMobile .nameBlock {
|
||||
display: inline;
|
||||
float: left;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
font-size: 10pt;
|
||||
border-left: none;
|
||||
border-top: none;
|
||||
z-index: 1000;
|
||||
z-index: 3;
|
||||
background-color: var(--challenge-modal-background-color);
|
||||
border: var(--challenge-modal-border);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
}
|
||||
|
||||
.setting {
|
||||
text-transform: capitalize;
|
||||
margin-left: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background-color: rgba(0, 0, 0, 0.25);
|
||||
z-index: 900;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.settingsModal label, .settingsModal button, .settingsModal select {
|
||||
@@ -31,9 +31,9 @@
|
||||
overflow-y: auto;
|
||||
margin-left: -178px;
|
||||
position: fixed;
|
||||
z-index: 1000;
|
||||
z-index: 5;
|
||||
padding: 2px;
|
||||
font-size: 14px;
|
||||
font-size: var(--settings-modal-font-size);
|
||||
box-shadow: 0 0 5px rgba(0, 0, 0, 0.25);
|
||||
background-color: var(--settings-modal-background-color);
|
||||
border-top: var(--settings-modal-border-top);
|
||||
|
||||
@@ -16,7 +16,7 @@ export const isValidURL = (url: string) => {
|
||||
};
|
||||
|
||||
export const copyShareLinkToClipboard = (subplebbitAddress: string, cid: string) => {
|
||||
const shareLink = `https://pleb.bz/p/${subplebbitAddress}/c/${cid}`;
|
||||
const shareLink = `https://pleb.bz/p/${subplebbitAddress}/c/${cid}?redirect=plebchan.eth.limo`;
|
||||
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(shareLink);
|
||||
|
||||
@@ -14,7 +14,7 @@ export const isCatalogView = (pathname: string): boolean => {
|
||||
|
||||
export const isDescriptionView = (pathname: string, params: ParamsType): boolean => {
|
||||
const decodedPathname = decodeURIComponent(pathname);
|
||||
return params.subplebbitAddress ? decodedPathname.startsWith(`/p/${params.subplebbitAddress}/description`) : false;
|
||||
return pathname === '/p/all/description' ? true : params.subplebbitAddress ? decodedPathname.startsWith(`/p/${params.subplebbitAddress}/description`) : false;
|
||||
};
|
||||
|
||||
export const isHomeView = (pathname: string): boolean => {
|
||||
|
||||
+68
-16
@@ -103,6 +103,30 @@
|
||||
--post-greentext-color: #789922;
|
||||
--post-replies-expand-button-background-image: url("/public/assets/buttons/plus-red.png");
|
||||
|
||||
/* post form */
|
||||
--post-form-toggle-font-size: 22px;
|
||||
--post-form-toggle-font-weight: 700;
|
||||
--post-form-field-title-background-color: #ea8;
|
||||
--post-form-field-title-color: #800;
|
||||
--post-form-field-border: 1px solid #800;
|
||||
--post-form-field-title-font-weight: 700;
|
||||
--post-form-field-title-padding: 0 5px;
|
||||
--post-form-field-title-font-size: 10pt;
|
||||
--post-form-field-input-border: 1px solid #aaa;
|
||||
--post-form-field-font-family: arial, helvetica, sans-serif;
|
||||
--post-form-field-input-appearance: none;
|
||||
--post-form-field-input-outline: none;
|
||||
--post-form-field-input-focus-border: 1px solid #ea8;
|
||||
--post-form-field-input-color: #000;
|
||||
|
||||
/* post menu */
|
||||
--post-menu-border-bottom: 2px solid #d9bfb7;
|
||||
--post-menu-border-right: 1px solid #d9bfb7;
|
||||
--post-menu-background-color: #f0e0d6;
|
||||
--post-menu-item-border-color: #d9bfb7;
|
||||
--post-menu-item-background-color: #f0e0d6;
|
||||
--post-menu-item-hover-background-color: #ffe;
|
||||
|
||||
/* post mobile */
|
||||
--post-mobile-background-color: #f5e9e1;
|
||||
--post-mobile-info-border-bottom: 1px solid #d9bfb7;
|
||||
@@ -120,22 +144,6 @@
|
||||
--post-mobile-file-info-text-color: #707070;
|
||||
--post-mobile-content-font-size: 11pt;
|
||||
|
||||
/* post form */
|
||||
--post-form-toggle-font-size: 22px;
|
||||
--post-form-toggle-font-weight: 700;
|
||||
--post-form-field-title-background-color: #ea8;
|
||||
--post-form-field-title-color: #800;
|
||||
--post-form-field-border: 1px solid #800;
|
||||
--post-form-field-title-font-weight: 700;
|
||||
--post-form-field-title-padding: 0 5px;
|
||||
--post-form-field-title-font-size: 10pt;
|
||||
--post-form-field-input-border: 1px solid #aaa;
|
||||
--post-form-field-font-family: arial, helvetica, sans-serif;
|
||||
--post-form-field-input-appearance: none;
|
||||
--post-form-field-input-outline: none;
|
||||
--post-form-field-input-focus-border: 1px solid #ea8;
|
||||
--post-form-field-input-color: #000;
|
||||
|
||||
/* quotelink */
|
||||
--post-quotelink-text-color: navy;
|
||||
--post-quotelink-text-color-hover: red;
|
||||
@@ -152,6 +160,7 @@
|
||||
--reply-modal-field-input-border-focus: 1px solid #ea8;
|
||||
|
||||
/* settings modal */
|
||||
--settings-modal-font-size: 14px;
|
||||
--settings-modal-background-color: #f0e0d6;
|
||||
--settings-modal-header-border-bottom: 1px solid #d9bfb7;
|
||||
--settings-modal-border-top: none;
|
||||
@@ -288,6 +297,14 @@
|
||||
--post-form-field-input-focus-border: 1px solid #98e;
|
||||
--post-form-field-input-color: #000;
|
||||
|
||||
/* post menu */
|
||||
--post-menu-border-bottom: 2px solid #b7c5d9;
|
||||
--post-menu-border-right: 1px solid #b7c5d9;
|
||||
--post-menu-background-color: #d6daf0;
|
||||
--post-menu-item-border-color: #b7c5d9;
|
||||
--post-menu-item-background-color: #d6daf0;
|
||||
--post-menu-item-hover-background-color: #eef2ff;
|
||||
|
||||
/* post mobile */
|
||||
--post-mobile-background-color: #d6daf0;
|
||||
--post-mobile-info-border-bottom: 1px solid #b7c5d9;
|
||||
@@ -320,6 +337,7 @@
|
||||
--reply-modal-field-input-border-focus: 1px solid #98e;
|
||||
|
||||
/* settings modal */
|
||||
--settings-modal-font-size: 14px;
|
||||
--settings-modal-background-color: #d6daf0;
|
||||
--settings-modal-border-top: none;
|
||||
--settings-modal-border-right: 1px solid #b7c5d9;
|
||||
@@ -427,6 +445,14 @@
|
||||
--post-form-field-title-font-size: 16px;
|
||||
--post-form-field-input-color: #000;
|
||||
|
||||
/* post menu */
|
||||
--post-menu-border-bottom: 1px solid #d9bfb7;
|
||||
--post-menu-border-right: unset;
|
||||
--post-menu-background-color: #f0e0d6;
|
||||
--post-menu-item-border-color: #d9bfb7;
|
||||
--post-menu-item-background-color: #f0e0d6;
|
||||
--post-menu-item-hover-background-color: #ffe;
|
||||
|
||||
/* post mobile */
|
||||
--post-mobile-background-color: #f5e9e1;
|
||||
--post-mobile-info-border-bottom: 1px solid #d9bfb7;
|
||||
@@ -576,6 +602,14 @@
|
||||
--post-form-field-title-font-size: 16px;
|
||||
--post-form-field-input-color: #000;
|
||||
|
||||
/* post menu */
|
||||
--post-menu-border-bottom: 1px solid #b7c5d9;
|
||||
--post-menu-border-right: unset;
|
||||
--post-menu-background-color: #d6daf0;
|
||||
--post-menu-item-border-color: #b7c5d9;
|
||||
--post-menu-item-background-color: #d6daf0;
|
||||
--post-menu-item-hover-background-color: #eef2ff;
|
||||
|
||||
/* post mobile */
|
||||
--post-mobile-background-color: #d6daf0;
|
||||
--post-mobile-info-border-bottom: 1px solid #b7c5d9;
|
||||
@@ -733,6 +767,14 @@
|
||||
--post-form-field-input-appearance: none;
|
||||
--post-form-field-input-outline: none;
|
||||
|
||||
/* post menu */
|
||||
--post-menu-border-bottom: 1px solid #000;
|
||||
--post-menu-border-right: unset;
|
||||
--post-menu-background-color: #282a2e;
|
||||
--post-menu-item-border-color: #000;
|
||||
--post-menu-item-background-color: #282a2e;
|
||||
--post-menu-item-hover-background-color: #1d1f21;
|
||||
|
||||
/* post mobile */
|
||||
--post-mobile-background-color: #282A2E;
|
||||
--post-mobile-info-border-top: 1px solid #2D2F33;
|
||||
@@ -774,6 +816,7 @@
|
||||
--select-color: #c5c8c6;
|
||||
|
||||
/* settings modal */
|
||||
--settings-modal-font-size: 14px;
|
||||
--settings-modal-background-color: #282a2e;
|
||||
--settings-modal-border-top: 1px solid #111;
|
||||
--settings-modal-border-right: 1px solid #111;
|
||||
@@ -908,6 +951,14 @@
|
||||
--post-form-field-input-focus-border: 1px solid #ea8;
|
||||
--post-form-field-input-color: #000;
|
||||
|
||||
/* post menu */
|
||||
--post-menu-border-bottom: 1px solid #ccc;
|
||||
--post-menu-border-right: unset;
|
||||
--post-menu-background-color: #ddd;
|
||||
--post-menu-item-border-color: #ccc;
|
||||
--post-menu-item-background-color: #ddd;
|
||||
--post-menu-item-hover-background-color: #eee;
|
||||
|
||||
/* quotelink */
|
||||
--post-quotelink-text-color: #f60;
|
||||
--post-quotelink-text-color-hover: #f30;
|
||||
@@ -924,6 +975,7 @@
|
||||
--reply-modal-field-input-border-focus: 1px solid #ea8;
|
||||
|
||||
/* settings modal */
|
||||
--settings-modal-font-size: 14px;
|
||||
--settings-modal-background-color: #ddd;
|
||||
--settings-modal-border-top: 1px solid #ccc;
|
||||
--settings-modal-border-right: 1px solid #ccc;
|
||||
|
||||
@@ -54,7 +54,7 @@ const Board = () => {
|
||||
{state === 'failed' ? (
|
||||
state
|
||||
) : isInSubscriptionsView && subscriptions?.length === 0 ? (
|
||||
`You haven't subscribed to any board yet.`
|
||||
`You have not subscribed to any board yet.`
|
||||
) : (
|
||||
<LoadingEllipsis string={loadingStateString} />
|
||||
)}
|
||||
@@ -78,15 +78,15 @@ const Board = () => {
|
||||
const setLastVirtuosoState = () => {
|
||||
virtuosoRef.current?.getState((snapshot: StateSnapshot) => {
|
||||
if (snapshot?.ranges?.length) {
|
||||
lastVirtuosoStates[subplebbitAddress + sortType] = snapshot;
|
||||
lastVirtuosoStates[location.pathname] = snapshot;
|
||||
}
|
||||
});
|
||||
};
|
||||
window.addEventListener('scroll', setLastVirtuosoState);
|
||||
return () => window.removeEventListener('scroll', setLastVirtuosoState);
|
||||
}, [subplebbitAddress, sortType]);
|
||||
}, [location.pathname]);
|
||||
|
||||
const lastVirtuosoState = lastVirtuosoStates?.[subplebbitAddress + sortType];
|
||||
const lastVirtuosoState = lastVirtuosoStates?.[location.pathname];
|
||||
|
||||
useEffect(() => {
|
||||
document.title = title ? title : shortAddress;
|
||||
|
||||
@@ -6,6 +6,7 @@ import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
||||
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
||||
import useFeedStateString from '../../hooks/use-feed-state-string';
|
||||
import { useMultisubMetadata } from '../../hooks/use-default-subplebbits';
|
||||
import useWindowWidth from '../../hooks/use-window-width';
|
||||
import CatalogRow from '../../components/catalog-row';
|
||||
import LoadingEllipsis from '../../components/loading-ellipsis';
|
||||
@@ -20,23 +21,27 @@ const useFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolean, subp
|
||||
const { address, createdAt, description, rules, shortAddress, suggested, title } = subplebbit || {};
|
||||
const { avatarUrl } = suggested || {};
|
||||
|
||||
const location = useLocation();
|
||||
const isInAllView = isAllView(location.pathname);
|
||||
const multisub = useMultisubMetadata();
|
||||
|
||||
const feedWithDescriptionAndRules = useMemo(() => {
|
||||
if (!isFeedLoaded) {
|
||||
return []; // prevent rules and description from appearing while feed is loading
|
||||
}
|
||||
if (!description && !rules) {
|
||||
if (!description && !rules && !isInAllView) {
|
||||
return feed;
|
||||
}
|
||||
const _feed = [...feed];
|
||||
if (description && description.length > 0) {
|
||||
if ((description && description.length > 0) || isInAllView) {
|
||||
_feed.unshift({
|
||||
isDescription: true,
|
||||
subplebbitAddress: address,
|
||||
timestamp: createdAt,
|
||||
author: { displayName: '## Board Mods' },
|
||||
content: description,
|
||||
content: isInAllView ? multisub?.description : description,
|
||||
link: avatarUrl,
|
||||
title: t('welcome_to_board', { board: title || `p/${shortAddress}` }),
|
||||
title: t('welcome_to_board', { board: isInAllView ? multisub?.title : title || `p/${shortAddress}` }),
|
||||
pinned: true,
|
||||
locked: true,
|
||||
});
|
||||
@@ -54,7 +59,7 @@ const useFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolean, subp
|
||||
});
|
||||
}
|
||||
return _feed;
|
||||
}, [feed, description, rules, address, isFeedLoaded, createdAt, title, shortAddress, avatarUrl, t]);
|
||||
}, [feed, description, rules, address, isFeedLoaded, createdAt, title, shortAddress, avatarUrl, t, isInAllView, multisub]);
|
||||
|
||||
// Memoize rows calculation, ensuring it updates on changes to the modified feed or column count
|
||||
const rows = useMemo(() => {
|
||||
@@ -102,7 +107,17 @@ const Catalog = () => {
|
||||
const subplebbit = useSubplebbit({ subplebbitAddress });
|
||||
const { shortAddress, state, title } = subplebbit || {};
|
||||
const loadingStateString = useFeedStateString(subplebbitAddresses) || t('loading');
|
||||
const loadingString = <div className={styles.stateString}>{state === 'failed' ? state : <LoadingEllipsis string={loadingStateString} />}</div>;
|
||||
const loadingString = (
|
||||
<div className={styles.stateString}>
|
||||
{state === 'failed' ? (
|
||||
state
|
||||
) : isInSubscriptionsView && subscriptions?.length === 0 ? (
|
||||
`You haven't subscribed to any board yet.`
|
||||
) : (
|
||||
<LoadingEllipsis string={loadingStateString} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
const Footer = () => {
|
||||
let footerContent;
|
||||
@@ -126,14 +141,14 @@ const Catalog = () => {
|
||||
const setLastVirtuosoState = () =>
|
||||
virtuosoRef.current?.getState((snapshot: StateSnapshot) => {
|
||||
if (snapshot?.ranges?.length) {
|
||||
lastVirtuosoStates[subplebbitAddress + 'catalog'] = snapshot;
|
||||
lastVirtuosoStates[location.pathname] = snapshot;
|
||||
}
|
||||
});
|
||||
window.addEventListener('scroll', setLastVirtuosoState);
|
||||
return () => window.removeEventListener('scroll', setLastVirtuosoState);
|
||||
}, [subplebbitAddress]);
|
||||
}, [location.pathname]);
|
||||
|
||||
const lastVirtuosoState = lastVirtuosoStates?.[subplebbitAddress + 'catalog'];
|
||||
const lastVirtuosoState = lastVirtuosoStates?.[location.pathname];
|
||||
|
||||
useEffect(() => {
|
||||
let documentTitle = title ? title : shortAddress;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.content {
|
||||
margin-bottom: 150px;
|
||||
}
|
||||
Reference in New Issue
Block a user