mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
refactor: remove description and rules mock posts
Removed all fake posts for subplebbit description and rules, including dedicated routes, components, and all related logic across the codebase. Board admins can now pin regular posts if they want to provide a description.
This commit is contained in:
@@ -164,13 +164,8 @@ const App = () => (
|
|||||||
<Route path='/:boardIdentifier/catalog' element={null} />
|
<Route path='/:boardIdentifier/catalog' element={null} />
|
||||||
<Route path='/:boardIdentifier/catalog/settings' element={null} />
|
<Route path='/:boardIdentifier/catalog/settings' element={null} />
|
||||||
|
|
||||||
<Route path='/all/description' element={<Post />} />
|
|
||||||
<Route path='/:boardIdentifier/thread/:commentCid' element={<Post />} />
|
<Route path='/:boardIdentifier/thread/:commentCid' element={<Post />} />
|
||||||
<Route path='/:boardIdentifier/thread/:commentCid/settings' element={<Post />} />
|
<Route path='/:boardIdentifier/thread/:commentCid/settings' element={<Post />} />
|
||||||
<Route path='/:boardIdentifier/description' element={<Post />} />
|
|
||||||
<Route path='/:boardIdentifier/description/settings' element={<Post />} />
|
|
||||||
<Route path='/:boardIdentifier/rules' element={<Post />} />
|
|
||||||
<Route path='/:boardIdentifier/rules/settings' element={<Post />} />
|
|
||||||
|
|
||||||
<Route path='/pending/:accountCommentIndex' element={<PendingPost />} />
|
<Route path='/pending/:accountCommentIndex' element={<PendingPost />} />
|
||||||
<Route path='/pending/:accountCommentIndex/settings' element={<PendingPost />} />
|
<Route path='/pending/:accountCommentIndex/settings' element={<PendingPost />} />
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
|
|||||||
import { useAccountComment, useSubscribe } from '@plebbit/plebbit-react-hooks';
|
import { useAccountComment, useSubscribe } from '@plebbit/plebbit-react-hooks';
|
||||||
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
||||||
import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits-pages';
|
import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits-pages';
|
||||||
import { isAllView, isCatalogView, isDescriptionView, isModView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
import { isAllView, isCatalogView, isModView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||||
import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
|
import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
|
||||||
import { getBoardPath, isDirectoryBoard } from '../../lib/utils/route-utils';
|
import { getBoardPath, isDirectoryBoard } from '../../lib/utils/route-utils';
|
||||||
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
|
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
|
||||||
@@ -365,23 +365,21 @@ const PostPageStats = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isInDescriptionView = isDescriptionView(location.pathname, params);
|
|
||||||
const resolvedAddress = useResolvedSubplebbitAddress();
|
const resolvedAddress = useResolvedSubplebbitAddress();
|
||||||
|
|
||||||
const comment = useSubplebbitsPagesStore((state) => state.comments[params?.commentCid as string]);
|
const comment = useSubplebbitsPagesStore((state) => state.comments[params?.commentCid as string]);
|
||||||
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[resolvedAddress as string]);
|
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[resolvedAddress as string]);
|
||||||
|
|
||||||
const descriptionReplyCount = location?.pathname.startsWith('/all/') ? 0 : subplebbit?.rules?.length > 0 ? 1 : 0;
|
|
||||||
const { closed, pinned, replyCount } = comment || {};
|
const { closed, pinned, replyCount } = comment || {};
|
||||||
const linkCount = useCountLinksInReplies(comment);
|
const linkCount = useCountLinksInReplies(comment);
|
||||||
|
|
||||||
const displayReplyCount = replyCount !== undefined ? replyCount.toString() : isInDescriptionView ? descriptionReplyCount : '?';
|
const displayReplyCount = replyCount !== undefined ? replyCount.toString() : '?';
|
||||||
const replyCountTooltip = replyCount !== undefined || isInDescriptionView ? _.capitalize(t('replies')) : t('loading');
|
const replyCountTooltip = replyCount !== undefined ? _.capitalize(t('replies')) : t('loading');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
{(pinned || isInDescriptionView) && `${_.capitalize(t('sticky'))} / `}
|
{pinned && `${_.capitalize(t('sticky'))} / `}
|
||||||
{(closed || isInDescriptionView) && `${_.capitalize(t('closed'))} / `}
|
{closed && `${_.capitalize(t('closed'))} / `}
|
||||||
<Tooltip children={displayReplyCount} content={replyCountTooltip} /> / <Tooltip children={linkCount?.toString()} content={_.capitalize(t('links'))} />
|
<Tooltip children={displayReplyCount} content={replyCountTooltip} /> / <Tooltip children={linkCount?.toString()} content={_.capitalize(t('links'))} />
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ interface CatalogPostMediaProps {
|
|||||||
linkHeight?: number;
|
linkHeight?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CatalogPostMedia = ({ cid, commentMediaInfo, isOutOfFeed, linkWidth, linkHeight }: CatalogPostMediaProps) => {
|
export const CatalogPostMedia = ({ cid, commentMediaInfo, linkWidth, linkHeight }: CatalogPostMediaProps) => {
|
||||||
const { patternThumbnailUrl, thumbnail, type, url } = commentMediaInfo || {};
|
const { patternThumbnailUrl, thumbnail, type, url } = commentMediaInfo || {};
|
||||||
const iframeThumbnail = patternThumbnailUrl || thumbnail;
|
const iframeThumbnail = patternThumbnailUrl || thumbnail;
|
||||||
const gifFrameUrl = useFetchGifFirstFrame(type === 'gif' ? url : undefined);
|
const gifFrameUrl = useFetchGifFirstFrame(type === 'gif' ? url : undefined);
|
||||||
@@ -58,7 +58,7 @@ export const CatalogPostMedia = ({ cid, commentMediaInfo, isOutOfFeed, linkWidth
|
|||||||
displayHeight = `${maxThumbnailSize}px`;
|
displayHeight = `${maxThumbnailSize}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'audio' || isOutOfFeed) {
|
if (type === 'audio') {
|
||||||
displayWidth = 'unset';
|
displayWidth = 'unset';
|
||||||
displayHeight = 'unset';
|
displayHeight = 'unset';
|
||||||
}
|
}
|
||||||
@@ -112,24 +112,7 @@ export const CatalogPostMedia = ({ cid, commentMediaInfo, isOutOfFeed, linkWidth
|
|||||||
|
|
||||||
const CatalogPost = ({ post }: { post: Comment }) => {
|
const CatalogPost = ({ post }: { post: Comment }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const {
|
const { author, cid, content, link, linkHeight, linkWidth, locked, pinned, replyCount, spoiler, subplebbitAddress, timestamp, title, thumbnailUrl } = post || {};
|
||||||
author,
|
|
||||||
cid,
|
|
||||||
content,
|
|
||||||
isDescription,
|
|
||||||
isRules,
|
|
||||||
link,
|
|
||||||
linkHeight,
|
|
||||||
linkWidth,
|
|
||||||
locked,
|
|
||||||
pinned,
|
|
||||||
replyCount,
|
|
||||||
spoiler,
|
|
||||||
subplebbitAddress,
|
|
||||||
timestamp,
|
|
||||||
title,
|
|
||||||
thumbnailUrl,
|
|
||||||
} = post || {};
|
|
||||||
const linkCount = useCountLinksInReplies(post);
|
const linkCount = useCountLinksInReplies(post);
|
||||||
|
|
||||||
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
@@ -145,7 +128,7 @@ const CatalogPost = ({ post }: { post: Comment }) => {
|
|||||||
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : '';
|
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : '';
|
||||||
const postMenuProps = useMemo(() => selectPostMenuProps(post), [post]);
|
const postMenuProps = useMemo(() => selectPostMenuProps(post), [post]);
|
||||||
|
|
||||||
const postLink = isInAllView && isDescription ? '/all/description' : `/${boardPath}/${isDescription ? 'description' : isRules ? 'rules' : `thread/${cid}`}`;
|
const postLink = boardPath ? `/${boardPath}/thread/${cid}` : `/thread/${cid}`;
|
||||||
|
|
||||||
const threadIcons = (
|
const threadIcons = (
|
||||||
<div className={styles.threadIcons}>
|
<div className={styles.threadIcons}>
|
||||||
@@ -236,12 +219,12 @@ const CatalogPost = ({ post }: { post: Comment }) => {
|
|||||||
'--maxHeight': maxHeight,
|
'--maxHeight': maxHeight,
|
||||||
} as React.CSSProperties;
|
} as React.CSSProperties;
|
||||||
|
|
||||||
const isTextOnlyThread = !hasThumbnail || isRules;
|
const isTextOnlyThread = !hasThumbnail;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={`${styles.post} ${imageSize === 'Large' ? styles.large : ''}`} style={CSSProperties}>
|
<div className={`${styles.post} ${imageSize === 'Large' ? styles.large : ''}`} style={CSSProperties}>
|
||||||
<div onMouseOver={() => setHoveredCid(isDescription ? 'd' : isRules ? 'r' : cid)} onMouseLeave={() => setHoveredCid(null)}>
|
<div onMouseOver={() => setHoveredCid(cid)} onMouseLeave={() => setHoveredCid(null)}>
|
||||||
{hidden ? (
|
{hidden ? (
|
||||||
<Link to={postLink}>
|
<Link to={postLink}>
|
||||||
<span className={styles.hiddenThumbnail} />
|
<span className={styles.hiddenThumbnail} />
|
||||||
@@ -266,13 +249,7 @@ const CatalogPost = ({ post }: { post: Comment }) => {
|
|||||||
{spoiler ? (
|
{spoiler ? (
|
||||||
<img src='assets/spoiler.png' alt='' />
|
<img src='assets/spoiler.png' alt='' />
|
||||||
) : (
|
) : (
|
||||||
<CatalogPostMedia
|
<CatalogPostMedia cid={cid} commentMediaInfo={commentMediaInfo} linkWidth={linkWidth} linkHeight={linkHeight} />
|
||||||
cid={cid}
|
|
||||||
commentMediaInfo={commentMediaInfo}
|
|
||||||
isOutOfFeed={isDescription || isRules}
|
|
||||||
linkWidth={linkWidth}
|
|
||||||
linkHeight={linkHeight}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -295,7 +272,7 @@ const CatalogPost = ({ post }: { post: Comment }) => {
|
|||||||
<div className={styles.postContent}>{(showOPComment || isTextOnlyThread) && (hasThumbnail ? postContent : <Link to={postLink}>{postContent}</Link>)}</div>
|
<div className={styles.postContent}>{(showOPComment || isTextOnlyThread) && (hasThumbnail ? postContent : <Link to={postLink}>{postContent}</Link>)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{(hoveredCid === cid || isDescription) &&
|
{hoveredCid === cid &&
|
||||||
showPortal &&
|
showPortal &&
|
||||||
createPortal(
|
createPortal(
|
||||||
<div className={styles.postPreview} ref={refs.setFloating} style={floatingStyles}>
|
<div className={styles.postPreview} ref={refs.setFloating} style={floatingStyles}>
|
||||||
@@ -307,7 +284,7 @@ const CatalogPost = ({ post }: { post: Comment }) => {
|
|||||||
) : (
|
) : (
|
||||||
t('posted_by')
|
t('posted_by')
|
||||||
)}{' '}
|
)}{' '}
|
||||||
<span className={`${styles.postAuthor} ${(isCatalogPostAuthorMod || isRules || isDescription) && styles.capcode}`}>
|
<span className={`${styles.postAuthor} ${isCatalogPostAuthorMod && styles.capcode}`}>
|
||||||
{author?.displayName || _.capitalize(t('anonymous'))}
|
{author?.displayName || _.capitalize(t('anonymous'))}
|
||||||
{isCatalogPostAuthorMod && <span className='capitalize'>{` ## Board ${catalogPostAuthorRole}`}</span>}
|
{isCatalogPostAuthorMod && <span className='capitalize'>{` ## Board ${catalogPostAuthorRole}`}</span>}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ const CommentContent = ({ comment: post }: { comment: Comment }) => {
|
|||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
// TODO: commentAuthor is not yet available outside of editedComment, wait for API to be updated
|
// TODO: commentAuthor is not yet available outside of editedComment, wait for API to be updated
|
||||||
const { cid, content, deleted, edit, isRules, original, parentCid, postCid, reason, removed, state } = post || {};
|
const { cid, content, deleted, edit, original, parentCid, postCid, reason, removed, state } = post || {};
|
||||||
// const banned = !!post?.commentAuthor?.banExpiresAt;
|
// const banned = !!post?.commentAuthor?.banExpiresAt;
|
||||||
|
|
||||||
const [showFullComment, setShowFullComment] = useState(false);
|
const [showFullComment, setShowFullComment] = useState(false);
|
||||||
@@ -45,7 +45,7 @@ const CommentContent = ({ comment: post }: { comment: Comment }) => {
|
|||||||
const loadingString = <div className={styles.stateString}>{stateString !== 'Failed' ? <LoadingEllipsis string={stateString || t('loading')} /> : stateString}</div>;
|
const loadingString = <div className={styles.stateString}>{stateString !== 'Failed' ? <LoadingEllipsis string={stateString || t('loading')} /> : stateString}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<blockquote className={`${styles.postMessage} ${!isReply && isMobile && styles.clampLines} ${isRules && styles.rulesMessage}`}>
|
<blockquote className={`${styles.postMessage} ${!isReply && isMobile && styles.clampLines}`}>
|
||||||
{isReply && state !== 'failed' && isReplyingToReply && !(deleted || removed) && <ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={quotelinkReply} />}
|
{isReply && state !== 'failed' && isReplyingToReply && !(deleted || removed) && <ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={quotelinkReply} />}
|
||||||
{removed ? (
|
{removed ? (
|
||||||
reason ? (
|
reason ? (
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ interface MediaProps {
|
|||||||
deleted?: boolean;
|
deleted?: boolean;
|
||||||
displayHeight?: string;
|
displayHeight?: string;
|
||||||
displayWidth?: string;
|
displayWidth?: string;
|
||||||
isDescription?: boolean;
|
|
||||||
isRules?: boolean;
|
|
||||||
isFloatingEmbed?: boolean;
|
isFloatingEmbed?: boolean;
|
||||||
isOutOfFeed?: boolean;
|
isOutOfFeed?: boolean;
|
||||||
isReply?: boolean;
|
isReply?: boolean;
|
||||||
@@ -204,9 +202,7 @@ const Image = ({ commentMediaInfo, displayHeight, displayWidth, isOutOfFeed, par
|
|||||||
const CommentMedia = ({
|
const CommentMedia = ({
|
||||||
commentMediaInfo,
|
commentMediaInfo,
|
||||||
deleted,
|
deleted,
|
||||||
isDescription,
|
|
||||||
isFloatingEmbed,
|
isFloatingEmbed,
|
||||||
isRules,
|
|
||||||
linkHeight,
|
linkHeight,
|
||||||
linkWidth,
|
linkWidth,
|
||||||
parentCid,
|
parentCid,
|
||||||
@@ -251,7 +247,7 @@ const CommentMedia = ({
|
|||||||
displayWidth = '100%';
|
displayWidth = '100%';
|
||||||
displayHeight = '100%';
|
displayHeight = '100%';
|
||||||
}
|
}
|
||||||
const isOutOfFeed = isDescription || isRules || isFloatingEmbed || spoiler || false; // virtuoso wrapper unneeded
|
const isOutOfFeed = isFloatingEmbed || spoiler || false; // virtuoso wrapper unneeded
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className={styles.content}>
|
<span className={styles.content}>
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ const timestampToDays = (timestamp: number) => {
|
|||||||
const EditMenu = ({ post }: { post: Comment }) => {
|
const EditMenu = ({ post }: { post: Comment }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const { author, cid, content, deleted, isRules, locked, parentCid, pinned, postCid, reason, removed, spoiler, subplebbitAddress } = post || {};
|
const { author, cid, content, deleted, locked, parentCid, pinned, postCid, reason, removed, spoiler, subplebbitAddress } = post || {};
|
||||||
const [isEditMenuOpen, setIsEditMenuOpen] = useState(false);
|
const [isEditMenuOpen, setIsEditMenuOpen] = useState(false);
|
||||||
const [isContentEditorOpen, setIsContentEditorOpen] = useState(false);
|
const [isContentEditorOpen, setIsContentEditorOpen] = useState(false);
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ const EditMenu = ({ post }: { post: Comment }) => {
|
|||||||
setIsEditMenuOpen(!isEditMenuOpen);
|
setIsEditMenuOpen(!isEditMenuOpen);
|
||||||
} else {
|
} else {
|
||||||
setIsEditMenuOpen(false);
|
setIsEditMenuOpen(false);
|
||||||
alert(parentCid || isRules ? t('cannot_edit_reply') : t('cannot_edit_thread'));
|
alert(parentCid ? t('cannot_edit_reply') : t('cannot_edit_thread'));
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
checked={isEditMenuOpen}
|
checked={isEditMenuOpen}
|
||||||
|
|||||||
@@ -58,8 +58,7 @@ const PostInfo = ({ post, postReplyCount = 0, roles, isHidden }: PostProps) => {
|
|||||||
const replies = useReplies(post);
|
const replies = useReplies(post);
|
||||||
const { address, shortAddress } = author || {};
|
const { address, shortAddress } = author || {};
|
||||||
const displayName = author?.displayName?.trim();
|
const displayName = author?.displayName?.trim();
|
||||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
const authorRole = roles?.[address]?.role.replace('moderator', 'mod');
|
||||||
const authorRole = roles?.[address]?.role.replace('moderator', 'mod') || (isDescription || isRules ? 'mod' : undefined);
|
|
||||||
const stateString = useStateString(post);
|
const stateString = useStateString(post);
|
||||||
const isReply = parentCid;
|
const isReply = parentCid;
|
||||||
const { showOmittedReplies } = useShowOmittedReplies();
|
const { showOmittedReplies } = useShowOmittedReplies();
|
||||||
@@ -139,66 +138,60 @@ const PostInfo = ({ post, postReplyCount = 0, roles, isHidden }: PostProps) => {
|
|||||||
</span>
|
</span>
|
||||||
)}{' '}
|
)}{' '}
|
||||||
</span>
|
</span>
|
||||||
{!(isDescription || isRules) && (
|
{author?.avatar && !(deleted || removed) && !hideAvatars && avatarImageUrl ? (
|
||||||
<>
|
<span className={styles.authorAvatar}>
|
||||||
{author?.avatar && !(deleted || removed) && !hideAvatars && avatarImageUrl ? (
|
<img src={avatarImageUrl} alt='' />
|
||||||
<span className={styles.authorAvatar}>
|
</span>
|
||||||
<img src={avatarImageUrl} alt='' />
|
) : null}
|
||||||
|
(ID:{' '}
|
||||||
|
{deleted ? (
|
||||||
|
t('deleted')
|
||||||
|
) : removed ? (
|
||||||
|
t('removed')
|
||||||
|
) : (
|
||||||
|
<Tooltip
|
||||||
|
children={
|
||||||
|
<span
|
||||||
|
title={t('highlight_posts')}
|
||||||
|
className={styles.userAddress}
|
||||||
|
onClick={() => handleUserAddressClick(userID, postCid)}
|
||||||
|
style={{ backgroundColor: userIDBackgroundColor, color: userIDTextColor }}
|
||||||
|
>
|
||||||
|
{userID}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
}
|
||||||
(ID:{' '}
|
content={`${numberOfPostsByAuthor === 1 ? t('1_post_by_this_id') : t('x_posts_by_this_id', { number: numberOfPostsByAuthor })}`}
|
||||||
{deleted ? (
|
showTooltip={isInPostPageView || showOmittedReplies[postCid] || (postReplyCount < 6 && !pinned)}
|
||||||
t('deleted')
|
/>
|
||||||
) : removed ? (
|
|
||||||
t('removed')
|
|
||||||
) : (
|
|
||||||
<Tooltip
|
|
||||||
children={
|
|
||||||
<span
|
|
||||||
title={t('highlight_posts')}
|
|
||||||
className={styles.userAddress}
|
|
||||||
onClick={() => handleUserAddressClick(userID, postCid)}
|
|
||||||
style={{ backgroundColor: userIDBackgroundColor, color: userIDTextColor }}
|
|
||||||
>
|
|
||||||
{userID}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
content={`${numberOfPostsByAuthor === 1 ? t('1_post_by_this_id') : t('x_posts_by_this_id', { number: numberOfPostsByAuthor })}`}
|
|
||||||
showTooltip={isInPostPageView || showOmittedReplies[postCid] || (postReplyCount < 6 && !pinned)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
){' '}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
){' '}
|
||||||
</span>
|
</span>
|
||||||
<span className={styles.dateTime}>
|
<span className={styles.dateTime}>
|
||||||
<Tooltip children={<span>{getFormattedDate(timestamp)}</span>} content={getFormattedTimeAgo(timestamp)} />
|
<Tooltip children={<span>{getFormattedDate(timestamp)}</span>} content={getFormattedTimeAgo(timestamp)} />{' '}
|
||||||
{isDescription || isRules ? '' : ' '}
|
|
||||||
</span>
|
</span>
|
||||||
<span className={styles.postNum}>
|
<span className={styles.postNum}>
|
||||||
{!(isDescription || isRules) &&
|
{cid ? (
|
||||||
(cid ? (
|
<span className={styles.postNumLink}>
|
||||||
<span className={styles.postNumLink}>
|
<Link
|
||||||
<Link
|
to={boardPath ? `/${boardPath}/thread/${cid}` : `/thread/${cid}`}
|
||||||
to={boardPath ? `/${boardPath}/thread/${cid}` : `/thread/${cid}`}
|
className={styles.linkToPost}
|
||||||
className={styles.linkToPost}
|
title={t('link_to_post')}
|
||||||
title={t('link_to_post')}
|
onClick={(e) => !cid && e.preventDefault()}
|
||||||
onClick={(e) => !cid && e.preventDefault()}
|
>
|
||||||
>
|
CID:
|
||||||
CID:
|
</Link>
|
||||||
</Link>
|
<span className={styles.replyToPost} title={t('reply_to_post')} onMouseDown={onReplyModalClick}>
|
||||||
<span className={styles.replyToPost} title={t('reply_to_post')} onMouseDown={onReplyModalClick}>
|
{shortCid}
|
||||||
{shortCid}
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
) : (
|
</span>
|
||||||
<>
|
) : (
|
||||||
<span>CID:</span>
|
<>
|
||||||
<span className={styles.pendingCid}>
|
<span>CID:</span>
|
||||||
{state === 'failed' || stateString === 'Failed' ? _.capitalize(t('failed')) : state === 'pending' ? _.capitalize(t('pending')) : ''}
|
<span className={styles.pendingCid}>
|
||||||
</span>
|
{state === 'failed' || stateString === 'Failed' ? _.capitalize(t('failed')) : state === 'pending' ? _.capitalize(t('pending')) : ''}
|
||||||
</>
|
</span>
|
||||||
))}
|
</>
|
||||||
|
)}
|
||||||
{pinned && (
|
{pinned && (
|
||||||
<span className={`${styles.stickyIconWrapper} ${!locked && styles.addPaddingBeforeReply}`}>
|
<span className={`${styles.stickyIconWrapper} ${!locked && styles.addPaddingBeforeReply}`}>
|
||||||
<img src='assets/icons/sticky.gif' alt='' className={styles.stickyIcon} title={t('sticky')} />
|
<img src='assets/icons/sticky.gif' alt='' className={styles.stickyIcon} title={t('sticky')} />
|
||||||
@@ -212,16 +205,7 @@ const PostInfo = ({ post, postReplyCount = 0, roles, isHidden }: PostProps) => {
|
|||||||
{!isInPostPageView && !isReply && !isHidden && (
|
{!isInPostPageView && !isReply && !isHidden && (
|
||||||
<span className={styles.replyButton}>
|
<span className={styles.replyButton}>
|
||||||
[
|
[
|
||||||
<Link
|
<Link to={boardPath ? `/${boardPath}/thread/${postCid}` : `/thread/${postCid}`} onClick={(e) => !cid && e.preventDefault()}>
|
||||||
to={
|
|
||||||
isInAllView && isDescription
|
|
||||||
? '/all/description'
|
|
||||||
: boardPath
|
|
||||||
? `/${boardPath}/${isDescription ? 'description' : isRules ? 'rules' : `thread/${postCid}`}`
|
|
||||||
: `/${isDescription ? 'description' : isRules ? 'rules' : `thread/${postCid}`}`
|
|
||||||
}
|
|
||||||
onClick={(e) => !cid && !isDescription && !isRules && e.preventDefault()}
|
|
||||||
>
|
|
||||||
{_.capitalize(t('reply'))}
|
{_.capitalize(t('reply'))}
|
||||||
</Link>
|
</Link>
|
||||||
]
|
]
|
||||||
@@ -246,8 +230,6 @@ const PostInfo = ({ post, postReplyCount = 0, roles, isHidden }: PostProps) => {
|
|||||||
interface PostMediaProps {
|
interface PostMediaProps {
|
||||||
commentMediaInfo: CommentMediaInfo | undefined;
|
commentMediaInfo: CommentMediaInfo | undefined;
|
||||||
hasThumbnail: boolean;
|
hasThumbnail: boolean;
|
||||||
isDescription: boolean;
|
|
||||||
isRules: boolean;
|
|
||||||
spoiler: boolean;
|
spoiler: boolean;
|
||||||
deleted: boolean;
|
deleted: boolean;
|
||||||
removed: boolean;
|
removed: boolean;
|
||||||
@@ -262,8 +244,6 @@ interface PostMediaProps {
|
|||||||
const PostMedia = ({
|
const PostMedia = ({
|
||||||
commentMediaInfo,
|
commentMediaInfo,
|
||||||
hasThumbnail,
|
hasThumbnail,
|
||||||
isDescription,
|
|
||||||
isRules,
|
|
||||||
spoiler,
|
spoiler,
|
||||||
deleted,
|
deleted,
|
||||||
removed,
|
removed,
|
||||||
@@ -330,14 +310,11 @@ const PostMedia = ({
|
|||||||
<CommentMedia
|
<CommentMedia
|
||||||
commentMediaInfo={commentMediaInfo}
|
commentMediaInfo={commentMediaInfo}
|
||||||
deleted={deleted}
|
deleted={deleted}
|
||||||
isDescription={isDescription}
|
|
||||||
isRules={isRules}
|
|
||||||
removed={removed}
|
removed={removed}
|
||||||
linkHeight={linkHeight}
|
linkHeight={linkHeight}
|
||||||
linkWidth={linkWidth}
|
linkWidth={linkWidth}
|
||||||
showThumbnail={showThumbnail}
|
showThumbnail={showThumbnail}
|
||||||
setShowThumbnail={setShowThumbnail}
|
setShowThumbnail={setShowThumbnail}
|
||||||
isOutOfFeed={isDescription || isRules}
|
|
||||||
parentCid={parentCid}
|
parentCid={parentCid}
|
||||||
spoiler={spoiler}
|
spoiler={spoiler}
|
||||||
/>
|
/>
|
||||||
@@ -356,7 +333,6 @@ const Reply = ({ postReplyCount, reply, roles }: PostProps) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { author, cid, deleted, link, linkHeight, linkWidth, postCid, reason, removed, spoiler, subplebbitAddress, thumbnailUrl, parentCid } = post || {};
|
const { author, cid, deleted, link, linkHeight, linkWidth, postCid, reason, removed, spoiler, subplebbitAddress, thumbnailUrl, parentCid } = post || {};
|
||||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
|
||||||
const defaultSubplebbits = useDefaultSubplebbits();
|
const defaultSubplebbits = useDefaultSubplebbits();
|
||||||
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : undefined;
|
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : undefined;
|
||||||
|
|
||||||
@@ -381,8 +357,6 @@ const Reply = ({ postReplyCount, reply, roles }: PostProps) => {
|
|||||||
<PostMedia
|
<PostMedia
|
||||||
commentMediaInfo={commentMediaInfo}
|
commentMediaInfo={commentMediaInfo}
|
||||||
hasThumbnail={hasThumbnail}
|
hasThumbnail={hasThumbnail}
|
||||||
isDescription={isDescription}
|
|
||||||
isRules={isRules}
|
|
||||||
spoiler={spoiler}
|
spoiler={spoiler}
|
||||||
deleted={deleted}
|
deleted={deleted}
|
||||||
removed={removed}
|
removed={removed}
|
||||||
@@ -403,7 +377,6 @@ const Reply = ({ postReplyCount, reply, roles }: PostProps) => {
|
|||||||
const PostDesktop = ({ post, roles, showAllReplies, showReplies = true }: PostProps) => {
|
const PostDesktop = ({ post, roles, showAllReplies, showReplies = true }: PostProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { author, cid, content, deleted, link, linkHeight, linkWidth, pinned, postCid, removed, spoiler, state, subplebbitAddress, thumbnailUrl, parentCid } = post || {};
|
const { author, cid, content, deleted, link, linkHeight, linkWidth, pinned, postCid, removed, spoiler, state, subplebbitAddress, thumbnailUrl, parentCid } = post || {};
|
||||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isInPendingPostView = isPendingPostView(location.pathname, params);
|
const isInPendingPostView = isPendingPostView(location.pathname, params);
|
||||||
@@ -427,17 +400,6 @@ const PostDesktop = ({ post, roles, showAllReplies, showReplies = true }: PostPr
|
|||||||
|
|
||||||
const stateString = useStateString(post) || t('downloading_board');
|
const stateString = useStateString(post) || t('downloading_board');
|
||||||
|
|
||||||
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]);
|
|
||||||
|
|
||||||
const subplebbitRulesReply = {
|
|
||||||
isRules: true,
|
|
||||||
subplebbitAddress,
|
|
||||||
timestamp: subplebbit?.createdAt,
|
|
||||||
author: { displayName: _.capitalize(t('anonymous')) },
|
|
||||||
content: `${subplebbit?.rules?.map((rule: string, index: number) => `${index + 1}. ${rule}`).join('\n')}`,
|
|
||||||
replyCount: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||||
|
|
||||||
@@ -451,7 +413,7 @@ const PostDesktop = ({ post, roles, showAllReplies, showReplies = true }: PostPr
|
|||||||
<div className={styles.replyQuotePreviewSpacer} />
|
<div className={styles.replyQuotePreviewSpacer} />
|
||||||
)}
|
)}
|
||||||
<div className={isHidden ? styles.postDesktopHidden : ''}>
|
<div className={isHidden ? styles.postDesktopHidden : ''}>
|
||||||
{!isInPostPageView && !isDescription && !isRules && showReplies && (
|
{!isInPostPageView && showReplies && (
|
||||||
<span className={`${styles.hideButtonWrapper} ${!hasThumbnail ? styles.hideButtonWrapperNoImage : ''}`}>
|
<span className={`${styles.hideButtonWrapper} ${!hasThumbnail ? styles.hideButtonWrapperNoImage : ''}`}>
|
||||||
<span className={`${styles.hideButton} ${hidden ? styles.unhideThread : styles.hideThread}`} onClick={hidden ? unhide : hide} />
|
<span className={`${styles.hideButton} ${hidden ? styles.unhideThread : styles.hideThread}`} onClick={hidden ? unhide : hide} />
|
||||||
</span>
|
</span>
|
||||||
@@ -462,8 +424,6 @@ const PostDesktop = ({ post, roles, showAllReplies, showReplies = true }: PostPr
|
|||||||
<PostMedia
|
<PostMedia
|
||||||
commentMediaInfo={commentMediaInfo}
|
commentMediaInfo={commentMediaInfo}
|
||||||
hasThumbnail={hasThumbnail}
|
hasThumbnail={hasThumbnail}
|
||||||
isDescription={isDescription}
|
|
||||||
isRules={isRules}
|
|
||||||
spoiler={spoiler}
|
spoiler={spoiler}
|
||||||
deleted={deleted}
|
deleted={deleted}
|
||||||
removed={removed}
|
removed={removed}
|
||||||
@@ -479,7 +439,7 @@ const PostDesktop = ({ post, roles, showAllReplies, showReplies = true }: PostPr
|
|||||||
{!isHidden && !content && !(deleted || removed) && <div className={styles.spacer} />}
|
{!isHidden && !content && !(deleted || removed) && <div className={styles.spacer} />}
|
||||||
{!isHidden && <CommentContent comment={post} />}
|
{!isHidden && <CommentContent comment={post} />}
|
||||||
</div>
|
</div>
|
||||||
{!isHidden && !isDescription && !isRules && !isInPendingPostView && (replyCount > 5 || (pinned && repliesCount > 0)) && !isInPostPageView && (
|
{!isHidden && !isInPendingPostView && (replyCount > 5 || (pinned && repliesCount > 0)) && !isInPostPageView && (
|
||||||
<span className={styles.summary}>
|
<span className={styles.summary}>
|
||||||
<span
|
<span
|
||||||
className={`${showOmittedReplies[cid] ? styles.hideOmittedReplies : styles.showOmittedReplies} ${styles.omittedRepliesButtonWrapper}`}
|
className={`${showOmittedReplies[cid] ? styles.hideOmittedReplies : styles.showOmittedReplies} ${styles.omittedRepliesButtonWrapper}`}
|
||||||
@@ -517,20 +477,8 @@ const PostDesktop = ({ post, roles, showAllReplies, showReplies = true }: PostPr
|
|||||||
<Reply reply={reply} roles={roles} postReplyCount={replyCount} />
|
<Reply reply={reply} roles={roles} postReplyCount={replyCount} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{isDescription && subplebbit?.rules && subplebbit?.rules.length > 0 && (
|
|
||||||
<div className={styles.replyContainer}>
|
|
||||||
<Reply reply={subplebbitRulesReply} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{!isInPendingPostView &&
|
{!isInPendingPostView && stateString && stateString !== 'Failed' && state !== 'succeeded' && isInPostPageView && !(!showReplies && !showAllReplies) ? (
|
||||||
(!isDescription || (isDescription && !subplebbit?.updatedAt)) &&
|
|
||||||
!isRules &&
|
|
||||||
stateString &&
|
|
||||||
stateString !== 'Failed' &&
|
|
||||||
state !== 'succeeded' &&
|
|
||||||
isInPostPageView &&
|
|
||||||
!(!showReplies && !showAllReplies) ? (
|
|
||||||
<div className={styles.stateString}>
|
<div className={styles.stateString}>
|
||||||
<br />
|
<br />
|
||||||
<LoadingEllipsis string={stateString} />
|
<LoadingEllipsis string={stateString} />
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ type PostMenuDesktopProps = {
|
|||||||
|
|
||||||
const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => {
|
const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { authorAddress, cid, isDescription, isRules, link, thumbnailUrl, linkWidth, linkHeight, postCid, subplebbitAddress } = postMenu || {};
|
const { authorAddress, cid, link, thumbnailUrl, linkWidth, linkHeight, postCid, subplebbitAddress } = postMenu || {};
|
||||||
const commentMediaInfo = getCommentMediaInfo(link || '', thumbnailUrl || '', linkWidth ?? 0, linkHeight ?? 0);
|
const commentMediaInfo = getCommentMediaInfo(link || '', thumbnailUrl || '', linkWidth ?? 0, linkHeight ?? 0);
|
||||||
const { thumbnail, type, url } = commentMediaInfo || {};
|
const { thumbnail, type, url } = commentMediaInfo || {};
|
||||||
const [menuBtnRotated, setMenuBtnRotated] = useState(false);
|
const [menuBtnRotated, setMenuBtnRotated] = useState(false);
|
||||||
@@ -152,7 +152,7 @@ const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => {
|
|||||||
const headingId = useId();
|
const headingId = useId();
|
||||||
|
|
||||||
const handleMenuClick = () => {
|
const handleMenuClick = () => {
|
||||||
if (cid || isDescription || isRules) {
|
if (cid) {
|
||||||
setMenuBtnRotated((prev) => !prev);
|
setMenuBtnRotated((prev) => !prev);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -166,21 +166,19 @@ const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => {
|
|||||||
className={isInCatalogView ? styles.postMenuBtnCatalog : styles.postMenuBtn}
|
className={isInCatalogView ? styles.postMenuBtnCatalog : styles.postMenuBtn}
|
||||||
title='Post menu'
|
title='Post menu'
|
||||||
onClick={handleMenuClick}
|
onClick={handleMenuClick}
|
||||||
style={{ transform: menuBtnRotated && (cid || isDescription || isRules) ? 'rotate(90deg)' : 'rotate(0deg)' }}
|
style={{ transform: menuBtnRotated && cid ? 'rotate(90deg)' : 'rotate(0deg)' }}
|
||||||
>
|
>
|
||||||
▶
|
▶
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
{menuBtnRotated &&
|
{menuBtnRotated &&
|
||||||
(cid || isDescription || isRules) &&
|
cid &&
|
||||||
createPortal(
|
createPortal(
|
||||||
<FloatingFocusManager context={context} modal={false}>
|
<FloatingFocusManager context={context} modal={false}>
|
||||||
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
|
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
|
||||||
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} linkType='thread' onClose={handleClose} />}
|
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} linkType='thread' onClose={handleClose} />}
|
||||||
{cid && <CopyContentIdButton cid={cid} onClose={handleClose} />}
|
{cid && <CopyContentIdButton cid={cid} onClose={handleClose} />}
|
||||||
{!cid && isDescription && subplebbitAddress && <CopyLinkButton subplebbitAddress={subplebbitAddress} linkType='description' onClose={handleClose} />}
|
{!(isInPostPageView && postCid === cid) && (
|
||||||
{!cid && isRules && subplebbitAddress && <CopyLinkButton subplebbitAddress={subplebbitAddress} linkType='rules' onClose={handleClose} />}
|
|
||||||
{!(isInPostPageView && postCid === cid) && !isDescription && !isRules && (
|
|
||||||
<div
|
<div
|
||||||
className={styles.postMenuItem}
|
className={styles.postMenuItem}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -192,8 +190,8 @@ const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButton url={url} onClose={handleClose} />}
|
{link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButton url={url} onClose={handleClose} />}
|
||||||
{!isDescription && !isRules && authorAddress && <BlockUserButton address={authorAddress} />}
|
{authorAddress && <BlockUserButton address={authorAddress} />}
|
||||||
{!isDescription && !isRules && (isInAllView || isInSubscriptionsView) && subplebbitAddress && <BlockBoardButton address={subplebbitAddress} />}
|
{(isInAllView || isInSubscriptionsView) && subplebbitAddress && <BlockBoardButton address={subplebbitAddress} />}
|
||||||
</div>
|
</div>
|
||||||
</FloatingFocusManager>,
|
</FloatingFocusManager>,
|
||||||
document.body,
|
document.body,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/s
|
|||||||
import { getHasThumbnail, getLinkMediaInfo } from '../../lib/utils/media-utils';
|
import { getHasThumbnail, getLinkMediaInfo } from '../../lib/utils/media-utils';
|
||||||
import { formatMarkdown } from '../../lib/utils/post-utils';
|
import { formatMarkdown } from '../../lib/utils/post-utils';
|
||||||
import { isValidURL } from '../../lib/utils/url-utils';
|
import { isValidURL } from '../../lib/utils/url-utils';
|
||||||
import { isAllView, isDescriptionView, isModView, isPostPageView, isRulesView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
import { isAllView, isModView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||||
import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
|
import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
|
||||||
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
|
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
|
||||||
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
|
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
|
||||||
@@ -357,9 +357,7 @@ const PostForm = () => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const isInDescriptionView = isDescriptionView(location.pathname, params);
|
|
||||||
const isInPostView = isPostPageView(location.pathname, params);
|
const isInPostView = isPostPageView(location.pathname, params);
|
||||||
const isInRulesView = isRulesView(location.pathname, params);
|
|
||||||
const isInAllView = isAllView(location.pathname);
|
const isInAllView = isAllView(location.pathname);
|
||||||
const isInModView = isModView(location.pathname);
|
const isInModView = isModView(location.pathname);
|
||||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
|
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
|
||||||
@@ -374,7 +372,7 @@ const PostForm = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { deleted, locked, removed, postCid } = comment || {};
|
const { deleted, locked, removed, postCid } = comment || {};
|
||||||
const isThreadClosed = deleted || locked || removed || isInDescriptionView || isInRulesView;
|
const isThreadClosed = deleted || locked || removed;
|
||||||
|
|
||||||
const [showForm, setShowForm] = useState(false);
|
const [showForm, setShowForm] = useState(false);
|
||||||
|
|
||||||
|
|||||||
@@ -135,8 +135,7 @@ type PostMenuMobileProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const PostMenuMobile = ({ postMenu, editMenuPost }: PostMenuMobileProps) => {
|
const PostMenuMobile = ({ postMenu, editMenuPost }: PostMenuMobileProps) => {
|
||||||
const { authorAddress, cid, deleted, isDescription, isRules, link, linkHeight, linkWidth, parentCid, postCid, removed, subplebbitAddress, thumbnailUrl } =
|
const { authorAddress, cid, deleted, link, linkHeight, linkWidth, parentCid, postCid, removed, subplebbitAddress, thumbnailUrl } = postMenu || {};
|
||||||
postMenu || {};
|
|
||||||
const { isAccountMod, isAccountCommentAuthor } = useEditCommentPrivileges({ commentAuthorAddress: authorAddress, subplebbitAddress });
|
const { isAccountMod, isAccountCommentAuthor } = useEditCommentPrivileges({ commentAuthorAddress: authorAddress, subplebbitAddress });
|
||||||
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
const { thumbnail, type, url } = commentMediaInfo || {};
|
const { thumbnail, type, url } = commentMediaInfo || {};
|
||||||
@@ -155,7 +154,7 @@ const PostMenuMobile = ({ postMenu, editMenuPost }: PostMenuMobileProps) => {
|
|||||||
const headingId = useId();
|
const headingId = useId();
|
||||||
|
|
||||||
const handleMenuClick = () => {
|
const handleMenuClick = () => {
|
||||||
if (cid || isDescription || isRules) {
|
if (cid) {
|
||||||
setIsMenuOpen((prev) => !prev);
|
setIsMenuOpen((prev) => !prev);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -172,17 +171,15 @@ const PostMenuMobile = ({ postMenu, editMenuPost }: PostMenuMobileProps) => {
|
|||||||
...
|
...
|
||||||
</span>
|
</span>
|
||||||
{isMenuOpen &&
|
{isMenuOpen &&
|
||||||
(cid || isDescription || isRules) &&
|
cid &&
|
||||||
createPortal(
|
createPortal(
|
||||||
<FloatingFocusManager context={context} modal={false}>
|
<FloatingFocusManager context={context} modal={false}>
|
||||||
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
|
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
|
||||||
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} linkType='thread' onClose={handleClose} />}
|
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} linkType='thread' onClose={handleClose} />}
|
||||||
{cid && <CopyContentIdButton cid={cid} onClose={handleClose} />}
|
{cid && <CopyContentIdButton cid={cid} onClose={handleClose} />}
|
||||||
{!cid && isDescription && subplebbitAddress && <CopyLinkButton subplebbitAddress={subplebbitAddress} linkType='description' onClose={handleClose} />}
|
|
||||||
{!cid && isRules && subplebbitAddress && <CopyLinkButton subplebbitAddress={subplebbitAddress} linkType='rules' onClose={handleClose} />}
|
|
||||||
{cid && subplebbitAddress && <HidePostButton cid={cid} isReply={!!parentCid} postCid={postCid} onClose={handleClose} />}
|
{cid && subplebbitAddress && <HidePostButton cid={cid} isReply={!!parentCid} postCid={postCid} onClose={handleClose} />}
|
||||||
{cid && subplebbitAddress && !isDescription && !isRules && authorAddress && <BlockUserButton address={authorAddress} />}
|
{cid && subplebbitAddress && authorAddress && <BlockUserButton address={authorAddress} />}
|
||||||
{cid && subplebbitAddress && !isInBoardView && !isDescription && !isRules && <BlockBoardButton address={subplebbitAddress} />}
|
{cid && subplebbitAddress && !isInBoardView && <BlockBoardButton address={subplebbitAddress} />}
|
||||||
{link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButtons url={url} onClose={handleClose} />}
|
{link && isValidURL(link) && (type === 'image' || type === 'gif' || thumbnail) && url && <ImageSearchButtons url={url} onClose={handleClose} />}
|
||||||
</div>
|
</div>
|
||||||
</FloatingFocusManager>,
|
</FloatingFocusManager>,
|
||||||
|
|||||||
@@ -55,10 +55,9 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles }: PostProps) => {
|
|||||||
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : undefined;
|
const boardPath = subplebbitAddress ? getBoardPath(subplebbitAddress, defaultSubplebbits) : undefined;
|
||||||
const isReply = parentCid;
|
const isReply = parentCid;
|
||||||
const title = post?.title?.trim();
|
const title = post?.title?.trim();
|
||||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
|
||||||
const { address, shortAddress } = author || {};
|
const { address, shortAddress } = author || {};
|
||||||
const displayName = author?.displayName?.trim();
|
const displayName = author?.displayName?.trim();
|
||||||
const authorRole = roles?.[address]?.role.replace('moderator', 'mod') || (isDescription || isRules ? 'mod' : undefined);
|
const authorRole = roles?.[address]?.role.replace('moderator', 'mod');
|
||||||
const { imageUrl: avatarImageUrl } = useAuthorAvatar({ author });
|
const { imageUrl: avatarImageUrl } = useAuthorAvatar({ author });
|
||||||
const { hideAvatars } = useAvatarVisibilityStore();
|
const { hideAvatars } = useAvatarVisibilityStore();
|
||||||
|
|
||||||
@@ -134,37 +133,33 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles }: PostProps) => {
|
|||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
{!(isDescription || isRules) && (
|
{author?.avatar && !(deleted || removed) && !hideAvatars && avatarImageUrl ? (
|
||||||
<>
|
<span className={styles.authorAvatar}>
|
||||||
{author?.avatar && !(deleted || removed) && !hideAvatars && avatarImageUrl ? (
|
<img src={avatarImageUrl} alt='' />
|
||||||
<span className={styles.authorAvatar}>
|
</span>
|
||||||
<img src={avatarImageUrl} alt='' />
|
) : null}
|
||||||
|
(ID: {''}
|
||||||
|
{removed ? (
|
||||||
|
_.lowerCase(t('removed'))
|
||||||
|
) : deleted ? (
|
||||||
|
_.lowerCase(t('deleted'))
|
||||||
|
) : (
|
||||||
|
<Tooltip
|
||||||
|
children={
|
||||||
|
<span
|
||||||
|
title={t('highlight_posts')}
|
||||||
|
className={styles.userAddress}
|
||||||
|
onClick={() => handleUserAddressClick(userID, postCid)}
|
||||||
|
style={{ backgroundColor: userIDBackgroundColor, color: userIDTextColor }}
|
||||||
|
>
|
||||||
|
{userID}
|
||||||
</span>
|
</span>
|
||||||
) : null}
|
}
|
||||||
(ID: {''}
|
content={`${numberOfPostsByAuthor === 1 ? t('1_post_by_this_id') : t('x_posts_by_this_id', { number: numberOfPostsByAuthor })}`}
|
||||||
{removed ? (
|
showTooltip={isInPostPageView || postReplyCount < 6}
|
||||||
_.lowerCase(t('removed'))
|
/>
|
||||||
) : deleted ? (
|
|
||||||
_.lowerCase(t('deleted'))
|
|
||||||
) : (
|
|
||||||
<Tooltip
|
|
||||||
children={
|
|
||||||
<span
|
|
||||||
title={t('highlight_posts')}
|
|
||||||
className={styles.userAddress}
|
|
||||||
onClick={() => handleUserAddressClick(userID, postCid)}
|
|
||||||
style={{ backgroundColor: userIDBackgroundColor, color: userIDTextColor }}
|
|
||||||
>
|
|
||||||
{userID}
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
content={`${numberOfPostsByAuthor === 1 ? t('1_post_by_this_id') : t('x_posts_by_this_id', { number: numberOfPostsByAuthor })}`}
|
|
||||||
showTooltip={isInPostPageView || postReplyCount < 6}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
){' '}
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
|
){' '}
|
||||||
{pinned && (
|
{pinned && (
|
||||||
<span className={styles.stickyIconWrapper}>
|
<span className={styles.stickyIconWrapper}>
|
||||||
<img src='assets/icons/sticky.gif' alt='' className={styles.stickyIcon} title={t('sticky')} />
|
<img src='assets/icons/sticky.gif' alt='' className={styles.stickyIcon} title={t('sticky')} />
|
||||||
@@ -196,29 +191,28 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles }: PostProps) => {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<Tooltip children={<span>{getFormattedDate(timestamp)}</span>} content={getFormattedTimeAgo(timestamp)} />{' '}
|
<Tooltip children={<span>{getFormattedDate(timestamp)}</span>} content={getFormattedTimeAgo(timestamp)} />{' '}
|
||||||
{!(isDescription || isRules) &&
|
{cid ? (
|
||||||
(cid ? (
|
<span className={styles.postNumLink}>
|
||||||
<span className={styles.postNumLink}>
|
<Link
|
||||||
<Link
|
to={boardPath ? `/${boardPath}/thread/${cid}` : `/thread/${cid}`}
|
||||||
to={boardPath ? `/${boardPath}/thread/${cid}` : `/thread/${cid}`}
|
className={styles.linkToPost}
|
||||||
className={styles.linkToPost}
|
title={t('link_to_post')}
|
||||||
title={t('link_to_post')}
|
onClick={(e) => !cid && e.preventDefault()}
|
||||||
onClick={(e) => !cid && e.preventDefault()}
|
>
|
||||||
>
|
CID:
|
||||||
CID:
|
</Link>
|
||||||
</Link>
|
<span className={styles.replyToPost} title={t('reply_to_post')} onMouseDown={onReplyModalClick}>
|
||||||
<span className={styles.replyToPost} title={t('reply_to_post')} onMouseDown={onReplyModalClick}>
|
{shortCid.slice(0, -4)}
|
||||||
{shortCid.slice(0, -4)}
|
|
||||||
</span>
|
|
||||||
</span>
|
</span>
|
||||||
) : (
|
</span>
|
||||||
<>
|
) : (
|
||||||
<span>CID:</span>
|
<>
|
||||||
<span className={styles.pendingCid}>
|
<span>CID:</span>
|
||||||
{state === 'failed' || stateString === 'Failed' ? _.capitalize(t('failed')) : state === 'pending' ? _.capitalize(t('pending')) : ''}
|
<span className={styles.pendingCid}>
|
||||||
</span>
|
{state === 'failed' || stateString === 'Failed' ? _.capitalize(t('failed')) : state === 'pending' ? _.capitalize(t('pending')) : ''}
|
||||||
</>
|
</span>
|
||||||
))}
|
</>
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -229,7 +223,6 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles }: PostProps) => {
|
|||||||
|
|
||||||
const PostMediaContent = ({ post, link }: { post: any; link: string }) => {
|
const PostMediaContent = ({ post, link }: { post: any; link: string }) => {
|
||||||
const [showThumbnail, setShowThumbnail] = useState(true);
|
const [showThumbnail, setShowThumbnail] = useState(true);
|
||||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
|
||||||
const { thumbnailUrl, linkWidth, linkHeight, spoiler, deleted, removed, parentCid } = post || {};
|
const { thumbnailUrl, linkWidth, linkHeight, spoiler, deleted, removed, parentCid } = post || {};
|
||||||
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||||
@@ -239,14 +232,11 @@ const PostMediaContent = ({ post, link }: { post: any; link: string }) => {
|
|||||||
<CommentMedia
|
<CommentMedia
|
||||||
commentMediaInfo={commentMediaInfo}
|
commentMediaInfo={commentMediaInfo}
|
||||||
deleted={deleted}
|
deleted={deleted}
|
||||||
isDescription={isDescription}
|
|
||||||
isRules={isRules}
|
|
||||||
removed={removed}
|
removed={removed}
|
||||||
linkHeight={linkHeight}
|
linkHeight={linkHeight}
|
||||||
linkWidth={linkWidth}
|
linkWidth={linkWidth}
|
||||||
showThumbnail={showThumbnail}
|
showThumbnail={showThumbnail}
|
||||||
setShowThumbnail={setShowThumbnail}
|
setShowThumbnail={setShowThumbnail}
|
||||||
isOutOfFeed={isDescription || isRules}
|
|
||||||
parentCid={parentCid}
|
parentCid={parentCid}
|
||||||
spoiler={spoiler}
|
spoiler={spoiler}
|
||||||
/>
|
/>
|
||||||
@@ -310,7 +300,6 @@ const Reply = ({ postReplyCount, reply, roles }: PostProps) => {
|
|||||||
const PostMobile = ({ post, roles, showAllReplies, showReplies = true }: PostProps) => {
|
const PostMobile = ({ post, roles, showAllReplies, showReplies = true }: PostProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { author, cid, pinned, postCid, replyCount, state, subplebbitAddress } = post || {};
|
const { author, cid, pinned, postCid, replyCount, state, subplebbitAddress } = post || {};
|
||||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isInAllView = isAllView(location.pathname);
|
const isInAllView = isAllView(location.pathname);
|
||||||
@@ -326,17 +315,6 @@ const PostMobile = ({ post, roles, showAllReplies, showReplies = true }: PostPro
|
|||||||
|
|
||||||
const stateString = useStateString(post) || t('loading_post');
|
const stateString = useStateString(post) || t('loading_post');
|
||||||
|
|
||||||
const subplebbit = useSubplebbitsStore((state) => state.subplebbits[subplebbitAddress]);
|
|
||||||
const showRules = isDescription && subplebbit?.rules && subplebbit?.rules.length > 0;
|
|
||||||
const subplebbitRulesReply = {
|
|
||||||
isRules: true,
|
|
||||||
subplebbitAddress,
|
|
||||||
timestamp: subplebbit?.createdAt,
|
|
||||||
author: { displayName: _.capitalize(t('anonymous')) },
|
|
||||||
content: `${subplebbit?.rules?.map((rule: string, index: number) => `${index + 1}. ${rule}`).join('\n')}`,
|
|
||||||
replyCount: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{hidden && !isInPostPageView ? (
|
{hidden && !isInPostPageView ? (
|
||||||
@@ -373,16 +351,7 @@ const PostMobile = ({ post, roles, showAllReplies, showReplies = true }: PostPro
|
|||||||
{replyCount > 0 && `${replyCount} Replies`}
|
{replyCount > 0 && `${replyCount} Replies`}
|
||||||
{linksCount > 0 && ` / ${linksCount} Links`}
|
{linksCount > 0 && ` / ${linksCount} Links`}
|
||||||
</span>
|
</span>
|
||||||
<Link
|
<Link to={boardPath ? `/${boardPath}/thread/${cid}` : `/thread/${cid}`} className='button'>
|
||||||
to={
|
|
||||||
isInAllView && isDescription
|
|
||||||
? '/all/description'
|
|
||||||
: boardPath
|
|
||||||
? `/${boardPath}/${isDescription ? 'description' : isRules ? 'rules' : `thread/${cid}`}`
|
|
||||||
: `/${isDescription ? 'description' : isRules ? 'rules' : `thread/${cid}`}`
|
|
||||||
}
|
|
||||||
className='button'
|
|
||||||
>
|
|
||||||
{t('view_thread')}
|
{t('view_thread')}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -400,20 +369,8 @@ const PostMobile = ({ post, roles, showAllReplies, showReplies = true }: PostPro
|
|||||||
<Reply postReplyCount={replyCount} reply={reply} roles={roles} />
|
<Reply postReplyCount={replyCount} reply={reply} roles={roles} />
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
{showRules && (
|
|
||||||
<div className={styles.replyContainer}>
|
|
||||||
<Reply reply={subplebbitRulesReply} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
{!isInPendingPostView &&
|
{!isInPendingPostView && stateString && stateString !== 'Failed' && state !== 'succeeded' && isInPostPageView && !(!showReplies && !showAllReplies) ? (
|
||||||
(!isDescription || (isDescription && !subplebbit?.updatedAt)) &&
|
|
||||||
!isRules &&
|
|
||||||
stateString &&
|
|
||||||
stateString !== 'Failed' &&
|
|
||||||
state !== 'succeeded' &&
|
|
||||||
isInPostPageView &&
|
|
||||||
!(!showReplies && !showAllReplies) ? (
|
|
||||||
<div className={styles.stateString}>
|
<div className={styles.stateString}>
|
||||||
<LoadingEllipsis string={stateString} />
|
<LoadingEllipsis string={stateString} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export { default } from './subplebbit-description';
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { Post } from '../../views/post';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { isAllView } from '../../lib/utils/view-utils';
|
|
||||||
import { useMultisubMetadata } from '../../hooks/use-default-subplebbits';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
interface DescriptionPostProps {
|
|
||||||
avatarUrl?: string;
|
|
||||||
createdAt: number;
|
|
||||||
description: string;
|
|
||||||
replyCount: number;
|
|
||||||
shortAddress: string;
|
|
||||||
subplebbitAddress: string | undefined;
|
|
||||||
title: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const SubplebbitDescription = ({ avatarUrl, createdAt, description, replyCount, shortAddress, subplebbitAddress, title }: DescriptionPostProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const location = useLocation();
|
|
||||||
const isInAllView = isAllView(location.pathname);
|
|
||||||
const multisubMetadata = useMultisubMetadata();
|
|
||||||
const descriptionSubplebbitAddress = isInAllView ? 'all' : subplebbitAddress;
|
|
||||||
|
|
||||||
const post = {
|
|
||||||
isDescription: true,
|
|
||||||
subplebbitAddress: descriptionSubplebbitAddress,
|
|
||||||
timestamp: isInAllView ? multisubMetadata?.createdAt : createdAt,
|
|
||||||
author: { displayName: _.capitalize(t('anonymous')) },
|
|
||||||
content: isInAllView ? multisubMetadata?.description : description,
|
|
||||||
link: avatarUrl,
|
|
||||||
replyCount,
|
|
||||||
title: t('welcome_to_board', {
|
|
||||||
board: isInAllView ? multisubMetadata?.title : title || `p/${shortAddress}`,
|
|
||||||
interpolation: { escapeValue: false },
|
|
||||||
}),
|
|
||||||
pinned: true,
|
|
||||||
locked: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Post post={post} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SubplebbitDescription;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export { default } from './subplebbit-rules';
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
import { Post } from '../../views/post';
|
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import _ from 'lodash';
|
|
||||||
|
|
||||||
interface RulesPostProps {
|
|
||||||
subplebbitAddress: string | undefined;
|
|
||||||
createdAt: number;
|
|
||||||
rules: string[];
|
|
||||||
}
|
|
||||||
|
|
||||||
const SubplebbitRules = ({ subplebbitAddress, createdAt, rules }: RulesPostProps) => {
|
|
||||||
const { t } = useTranslation();
|
|
||||||
const content = rules?.map((rule, index) => `${index + 1}. ${rule}`).join('\n');
|
|
||||||
const post = {
|
|
||||||
isRules: true,
|
|
||||||
subplebbitAddress,
|
|
||||||
timestamp: createdAt,
|
|
||||||
author: { displayName: _.capitalize(t('anonymous')) },
|
|
||||||
content,
|
|
||||||
replyCount: 0,
|
|
||||||
title: _.capitalize(t('rules')),
|
|
||||||
pinned: true,
|
|
||||||
locked: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
return <Post post={post} />;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default SubplebbitRules;
|
|
||||||
@@ -4,7 +4,6 @@ import { useAccountComment, useSubplebbitStats } from '@plebbit/plebbit-react-ho
|
|||||||
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
||||||
import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits-pages';
|
import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits-pages';
|
||||||
import useSubplebbitStatsVisibilityStore from '../../stores/use-subplebbit-stats-visibility-store';
|
import useSubplebbitStatsVisibilityStore from '../../stores/use-subplebbit-stats-visibility-store';
|
||||||
import { isDescriptionView, isRulesView } from '../../lib/utils/view-utils';
|
|
||||||
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
|
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
|
||||||
import styles from './subplebbit-stats.module.css';
|
import styles from './subplebbit-stats.module.css';
|
||||||
|
|
||||||
@@ -24,12 +23,10 @@ const SubplebbitStats = () => {
|
|||||||
const isHidden = hiddenStats[address];
|
const isHidden = hiddenStats[address];
|
||||||
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isInDescriptionView = isDescriptionView(location.pathname, params);
|
|
||||||
const isInRulesView = isRulesView(location.pathname, params);
|
|
||||||
|
|
||||||
const comment = useSubplebbitsPagesStore((state) => state.comments[params?.commentCid as string]);
|
const comment = useSubplebbitsPagesStore((state) => state.comments[params?.commentCid as string]);
|
||||||
const { deleted, locked, removed } = comment || {};
|
const { deleted, locked, removed } = comment || {};
|
||||||
const hideStats = deleted || locked || removed || isInDescriptionView || isInRulesView;
|
const hideStats = deleted || locked || removed;
|
||||||
|
|
||||||
const unixToMMDDYYYY = (timestamp: number) => {
|
const unixToMMDDYYYY = (timestamp: number) => {
|
||||||
const date = new Date(timestamp * 1000);
|
const date = new Date(timestamp * 1000);
|
||||||
|
|||||||
@@ -1,36 +1,19 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
import { useLocation } from 'react-router-dom';
|
|
||||||
import { useAccountComments, Subplebbit } from '@plebbit/plebbit-react-hooks';
|
import { useAccountComments, Subplebbit } from '@plebbit/plebbit-react-hooks';
|
||||||
import useInterfaceSettingsStore from '../stores/use-interface-settings-store';
|
import useInterfaceSettingsStore from '../stores/use-interface-settings-store';
|
||||||
import { getCommentMediaInfo, getHasThumbnail } from '../lib/utils/media-utils';
|
import { getCommentMediaInfo, getHasThumbnail } from '../lib/utils/media-utils';
|
||||||
import { isAllView } from '../lib/utils/view-utils';
|
|
||||||
import { useMultisubMetadata } from './use-default-subplebbits';
|
|
||||||
import _ from 'lodash';
|
|
||||||
import useCatalogFiltersStore from '../stores/use-catalog-filters-store';
|
|
||||||
|
|
||||||
const useCatalogFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolean, subplebbit: Subplebbit) => {
|
const useCatalogFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolean, subplebbit: Subplebbit) => {
|
||||||
const { t } = useTranslation();
|
const { address } = subplebbit || {};
|
||||||
const { address, createdAt, description, rules, shortAddress, suggested, title } = subplebbit || {};
|
|
||||||
const { avatarUrl } = suggested || {};
|
|
||||||
const { hideThreadsWithoutImages } = useInterfaceSettingsStore();
|
const { hideThreadsWithoutImages } = useInterfaceSettingsStore();
|
||||||
|
|
||||||
const location = useLocation();
|
|
||||||
const isInAllView = isAllView(location.pathname);
|
|
||||||
const multisub = useMultisubMetadata();
|
|
||||||
|
|
||||||
const { accountComments } = useAccountComments();
|
const { accountComments } = useAccountComments();
|
||||||
const { searchText } = useCatalogFiltersStore();
|
|
||||||
|
|
||||||
const feedWithFakePostsOnTop = useMemo(() => {
|
const feedWithFakePostsOnTop = useMemo(() => {
|
||||||
if (!isFeedLoaded) {
|
if (!isFeedLoaded) {
|
||||||
return []; // prevent rules and description from appearing while feed is loading
|
return []; // prevent rules and description from appearing while feed is loading
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!description && !rules && !isInAllView) {
|
|
||||||
return feed;
|
|
||||||
}
|
|
||||||
|
|
||||||
const _feed = [...feed];
|
const _feed = [...feed];
|
||||||
|
|
||||||
// show account comments instantly in the feed once published (cid defined), instead of waiting for the feed to update
|
// show account comments instantly in the feed once published (cid defined), instead of waiting for the feed to update
|
||||||
@@ -65,53 +48,8 @@ const useCatalogFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolea
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add subplebbit description and rules as fake posts at the top of the feed
|
|
||||||
if (description && description.length > 0 && !searchText) {
|
|
||||||
_feed.unshift({
|
|
||||||
isDescription: true,
|
|
||||||
subplebbitAddress: address,
|
|
||||||
timestamp: createdAt,
|
|
||||||
author: { displayName: `## ${t('board_mods')}` },
|
|
||||||
content: isInAllView ? multisub?.description : description,
|
|
||||||
link: avatarUrl,
|
|
||||||
title: t('welcome_to_board', { board: isInAllView ? multisub?.title : title || `p/${shortAddress}`, interpolation: { escapeValue: false } }),
|
|
||||||
pinned: true,
|
|
||||||
locked: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// rules are shown in description thread if both are set
|
|
||||||
if (rules && rules.length > 0 && !description && !searchText) {
|
|
||||||
_feed.unshift({
|
|
||||||
isRules: true,
|
|
||||||
subplebbitAddress: address,
|
|
||||||
timestamp: createdAt,
|
|
||||||
author: { displayName: `## ${t('board_mods')}` },
|
|
||||||
content: rules.map((rule: string, index: number) => `${index + 1}. ${rule}`).join('\n'),
|
|
||||||
title: _.capitalize(t('rules')),
|
|
||||||
pinned: true,
|
|
||||||
locked: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return _feed;
|
return _feed;
|
||||||
}, [
|
}, [accountComments, feed, address, isFeedLoaded, hideThreadsWithoutImages]);
|
||||||
accountComments,
|
|
||||||
feed,
|
|
||||||
description,
|
|
||||||
rules,
|
|
||||||
address,
|
|
||||||
isFeedLoaded,
|
|
||||||
createdAt,
|
|
||||||
title,
|
|
||||||
shortAddress,
|
|
||||||
avatarUrl,
|
|
||||||
t,
|
|
||||||
isInAllView,
|
|
||||||
multisub,
|
|
||||||
hideThreadsWithoutImages,
|
|
||||||
searchText,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const rows = useMemo(() => {
|
const rows = useMemo(() => {
|
||||||
const rows = [];
|
const rows = [];
|
||||||
|
|||||||
@@ -53,11 +53,6 @@ const useReplies = (comment: Comment) => {
|
|||||||
});
|
});
|
||||||
}, [flattenedReplies, accountRepliesNotYetPublished]);
|
}, [flattenedReplies, accountRepliesNotYetPublished]);
|
||||||
|
|
||||||
// if the comment is a fake comment for description or rules, return an empty array
|
|
||||||
if (comment?.isDescription || comment?.isRules) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
return repliesAndNotYetPublishedReplies;
|
return repliesAndNotYetPublishedReplies;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ export type PostMenuProps = {
|
|||||||
postCid?: string;
|
postCid?: string;
|
||||||
parentCid?: string;
|
parentCid?: string;
|
||||||
subplebbitAddress?: string;
|
subplebbitAddress?: string;
|
||||||
isDescription?: boolean;
|
|
||||||
isRules?: boolean;
|
|
||||||
authorAddress?: string;
|
authorAddress?: string;
|
||||||
link?: string;
|
link?: string;
|
||||||
linkWidth?: number;
|
linkWidth?: number;
|
||||||
@@ -21,8 +19,6 @@ export const selectPostMenuProps = (post?: Comment): PostMenuProps => ({
|
|||||||
postCid: post?.postCid,
|
postCid: post?.postCid,
|
||||||
parentCid: post?.parentCid,
|
parentCid: post?.parentCid,
|
||||||
subplebbitAddress: post?.subplebbitAddress,
|
subplebbitAddress: post?.subplebbitAddress,
|
||||||
isDescription: post?.isDescription,
|
|
||||||
isRules: post?.isRules,
|
|
||||||
authorAddress: post?.author?.address,
|
authorAddress: post?.author?.address,
|
||||||
link: post?.link,
|
link: post?.link,
|
||||||
linkWidth: post?.linkWidth,
|
linkWidth: post?.linkWidth,
|
||||||
|
|||||||
@@ -105,8 +105,6 @@ export const isFeedRoute = (pathname: string): boolean => {
|
|||||||
const normalizedPath = pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
|
const normalizedPath = pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
|
||||||
|
|
||||||
if (normalizedPath.includes('/thread/')) return false;
|
if (normalizedPath.includes('/thread/')) return false;
|
||||||
if (normalizedPath.endsWith('/description')) return false;
|
|
||||||
if (normalizedPath.endsWith('/rules')) return false;
|
|
||||||
if (normalizedPath.startsWith('/pending/')) return false;
|
if (normalizedPath.startsWith('/pending/')) return false;
|
||||||
|
|
||||||
const pathWithoutSettings = normalizedPath.replace(/\/settings$/, '');
|
const pathWithoutSettings = normalizedPath.replace(/\/settings$/, '');
|
||||||
@@ -130,8 +128,6 @@ export const isPostRoute = (pathname: string): boolean => {
|
|||||||
const normalizedPath = pathname.replace(/\/settings$/, '');
|
const normalizedPath = pathname.replace(/\/settings$/, '');
|
||||||
|
|
||||||
if (normalizedPath.includes('/thread/')) return true;
|
if (normalizedPath.includes('/thread/')) return true;
|
||||||
if (normalizedPath.endsWith('/description')) return true;
|
|
||||||
if (normalizedPath.endsWith('/rules')) return true;
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
@@ -150,10 +146,6 @@ export const getFeedCacheKey = (pathname: string): string | null => {
|
|||||||
return parts[0] || null;
|
return parts[0] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (normalizedPath.endsWith('/description') || normalizedPath.endsWith('/rules')) {
|
|
||||||
return normalizedPath.replace(/\/(description|rules)$/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (normalizedPath.startsWith('/pending/')) {
|
if (normalizedPath.startsWith('/pending/')) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,11 +17,10 @@ export const isValidURL = (url: string) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ShareLinkType = 'thread' | 'description' | 'rules';
|
export type ShareLinkType = 'thread';
|
||||||
|
|
||||||
// Copies a share link to clipboard for a board, thread, description, or rules page
|
// Copies a share link to clipboard for a board, thread, description, or rules page
|
||||||
export function copyShareLinkToClipboard(boardIdentifier: string, linkType: 'thread', cid: string): Promise<void>;
|
export function copyShareLinkToClipboard(boardIdentifier: string, linkType: 'thread', cid: string): Promise<void>;
|
||||||
export function copyShareLinkToClipboard(boardIdentifier: string, linkType: Exclude<ShareLinkType, 'thread'>, cid?: undefined): Promise<void>;
|
|
||||||
export async function copyShareLinkToClipboard(boardIdentifier: string, linkType: ShareLinkType, cid?: string): Promise<void> {
|
export async function copyShareLinkToClipboard(boardIdentifier: string, linkType: ShareLinkType, cid?: string): Promise<void> {
|
||||||
if (linkType === 'thread') {
|
if (linkType === 'thread') {
|
||||||
if (!cid) {
|
if (!cid) {
|
||||||
@@ -71,12 +70,10 @@ export const is5chanLink = (url: string): boolean => {
|
|||||||
// - /{boardIdentifier} (directory code or address)
|
// - /{boardIdentifier} (directory code or address)
|
||||||
// - /{boardIdentifier}/thread/{commentCid}
|
// - /{boardIdentifier}/thread/{commentCid}
|
||||||
// - /{boardIdentifier}/catalog
|
// - /{boardIdentifier}/catalog
|
||||||
// - /{boardIdentifier}/description
|
|
||||||
// - /{boardIdentifier}/rules
|
|
||||||
// - /all, /subs, /mod, /pending/{index}
|
// - /all, /subs, /mod, /pending/{index}
|
||||||
return (
|
return (
|
||||||
/^\/p\/[^/]+(\/c\/[^/]+)?$/.test(routePath) ||
|
/^\/p\/[^/]+(\/c\/[^/]+)?$/.test(routePath) ||
|
||||||
/^\/[^/]+(\/thread\/[^/]+|\/catalog|\/description|\/rules)?$/.test(routePath) ||
|
/^\/[^/]+(\/thread\/[^/]+|\/catalog)?$/.test(routePath) ||
|
||||||
/^\/(all|subscriptions|mod)(\/catalog|\/thread\/[^/]+)?(\/[^/]+)?$/.test(routePath) ||
|
/^\/(all|subscriptions|mod)(\/catalog|\/thread\/[^/]+)?(\/[^/]+)?$/.test(routePath) ||
|
||||||
/^\/pending\/[^/]+$/.test(routePath)
|
/^\/pending\/[^/]+$/.test(routePath)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -51,12 +51,6 @@ export const isCatalogView = (pathname: string, params: ParamsType): boolean =>
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isDescriptionView = (pathname: string, params: ParamsType): boolean => {
|
|
||||||
const decodedPathname = decodeURIComponent(pathname);
|
|
||||||
const identifier = params.boardIdentifier || params.subplebbitAddress;
|
|
||||||
return pathname === '/all/description' ? true : identifier ? decodedPathname.startsWith(`/${identifier}/description`) : false;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isHomeView = (pathname: string): boolean => {
|
export const isHomeView = (pathname: string): boolean => {
|
||||||
return pathname === '/';
|
return pathname === '/';
|
||||||
};
|
};
|
||||||
@@ -70,29 +64,17 @@ export const isPendingPostView = (pathname: string, params: ParamsType): boolean
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const isPostPageView = (pathname: string, params: ParamsType): boolean => {
|
export const isPostPageView = (pathname: string, params: ParamsType): boolean => {
|
||||||
if (isDescriptionView(pathname, params) || isRulesView(pathname, params)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
const decodedPathname = decodeURIComponent(pathname);
|
const decodedPathname = decodeURIComponent(pathname);
|
||||||
const identifier = params.boardIdentifier || params.subplebbitAddress;
|
const identifier = params.boardIdentifier || params.subplebbitAddress;
|
||||||
return identifier && params.commentCid ? decodedPathname.startsWith(`/${identifier}/thread/${params.commentCid}`) : false;
|
return identifier && params.commentCid ? decodedPathname.startsWith(`/${identifier}/thread/${params.commentCid}`) : false;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isRulesView = (pathname: string, params: ParamsType): boolean => {
|
|
||||||
const decodedPathname = decodeURIComponent(pathname);
|
|
||||||
const identifier = params.boardIdentifier || params.subplebbitAddress;
|
|
||||||
return identifier ? decodedPathname.startsWith(`/${identifier}/rules`) : false;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const isSettingsView = (pathname: string, params: ParamsType): boolean => {
|
export const isSettingsView = (pathname: string, params: ParamsType): boolean => {
|
||||||
const { accountCommentIndex, boardIdentifier, commentCid, subplebbitAddress } = params;
|
const { accountCommentIndex, boardIdentifier, commentCid, subplebbitAddress } = params;
|
||||||
const identifier = boardIdentifier || subplebbitAddress;
|
const identifier = boardIdentifier || subplebbitAddress;
|
||||||
const decodedPathname = decodeURIComponent(pathname);
|
const decodedPathname = decodeURIComponent(pathname);
|
||||||
return (
|
return (
|
||||||
(identifier && commentCid && decodedPathname === `/${identifier}/thread/${commentCid}/settings`) ||
|
(identifier && commentCid && decodedPathname === `/${identifier}/thread/${commentCid}/settings`) || decodedPathname === `/pending/${accountCommentIndex}/settings`
|
||||||
(identifier && decodedPathname === `/${identifier}/description/settings`) ||
|
|
||||||
(identifier && decodedPathname === `/${identifier}/rules/settings`) ||
|
|
||||||
decodedPathname === `/pending/${accountCommentIndex}/settings`
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -115,11 +97,9 @@ export const isNotFoundView = (pathname: string, params: ParamsType): boolean =>
|
|||||||
!isAllView(pathname) &&
|
!isAllView(pathname) &&
|
||||||
!isBoardView(pathname, params) &&
|
!isBoardView(pathname, params) &&
|
||||||
!isCatalogView(pathname, params) &&
|
!isCatalogView(pathname, params) &&
|
||||||
!isDescriptionView(pathname, params) &&
|
|
||||||
!isHomeView(pathname) &&
|
!isHomeView(pathname) &&
|
||||||
!isPendingPostView(pathname, params) &&
|
!isPendingPostView(pathname, params) &&
|
||||||
!isPostPageView(pathname, params) &&
|
!isPostPageView(pathname, params) &&
|
||||||
!isRulesView(pathname, params) &&
|
|
||||||
!isSettingsView(pathname, params) &&
|
!isSettingsView(pathname, params) &&
|
||||||
!isSubscriptionsView(pathname, params)
|
!isSubscriptionsView(pathname, params)
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ import useSortingStore from '../../stores/use-sorting-store';
|
|||||||
import { getSubplebbitAddress } from '../../lib/utils/route-utils';
|
import { getSubplebbitAddress } from '../../lib/utils/route-utils';
|
||||||
import ErrorDisplay from '../../components/error-display/error-display';
|
import ErrorDisplay from '../../components/error-display/error-display';
|
||||||
import LoadingEllipsis from '../../components/loading-ellipsis';
|
import LoadingEllipsis from '../../components/loading-ellipsis';
|
||||||
import SubplebbitDescription from '../../components/subplebbit-description';
|
|
||||||
import SubplebbitRules from '../../components/subplebbit-rules';
|
|
||||||
import { Post } from '../post';
|
import { Post } from '../post';
|
||||||
|
|
||||||
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
|
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
|
||||||
@@ -336,23 +334,11 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
|
|||||||
<>
|
<>
|
||||||
{shouldShowSnow() && <hr />}
|
{shouldShowSnow() && <hr />}
|
||||||
<div className={`${styles.content} ${shouldShowSnow() ? styles.garland : ''}`}>
|
<div className={`${styles.content} ${shouldShowSnow() ? styles.garland : ''}`}>
|
||||||
{((description && description.length > 0) || isInAllView) && (
|
|
||||||
<SubplebbitDescription
|
|
||||||
avatarUrl={suggested?.avatarUrl}
|
|
||||||
subplebbitAddress={subplebbitAddress}
|
|
||||||
createdAt={createdAt}
|
|
||||||
description={description}
|
|
||||||
replyCount={isInAllView ? 0 : rules?.length > 0 ? 1 : 0}
|
|
||||||
shortAddress={shortAddress}
|
|
||||||
title={title}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{shouldShowErrorToUser && (
|
{shouldShowErrorToUser && (
|
||||||
<div className={styles.error}>
|
<div className={styles.error}>
|
||||||
<ErrorDisplay error={error} />
|
<ErrorDisplay error={error} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{rules && !description && rules.length > 0 && <SubplebbitRules subplebbitAddress={subplebbitAddress} createdAt={createdAt} rules={rules} />}
|
|
||||||
<Virtuoso
|
<Virtuoso
|
||||||
increaseViewportBy={{ bottom: 1200, top: 1200 }}
|
increaseViewportBy={{ bottom: 1200, top: 1200 }}
|
||||||
totalCount={combinedFeed.length}
|
totalCount={combinedFeed.length}
|
||||||
|
|||||||
+2
-20
@@ -3,14 +3,12 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { Comment, Role, useComment, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
import { Comment, Role, useComment, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
||||||
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
||||||
import { useLocation, useParams } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import { isAllView, isDescriptionView, isRulesView } from '../../lib/utils/view-utils';
|
import { isAllView } from '../../lib/utils/view-utils';
|
||||||
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
|
import { useResolvedSubplebbitAddress } from '../../hooks/use-resolved-subplebbit-address';
|
||||||
import useIsMobile from '../../hooks/use-is-mobile';
|
import useIsMobile from '../../hooks/use-is-mobile';
|
||||||
import ErrorDisplay from '../../components/error-display/error-display';
|
import ErrorDisplay from '../../components/error-display/error-display';
|
||||||
import PostDesktop from '../../components/post-desktop';
|
import PostDesktop from '../../components/post-desktop';
|
||||||
import PostMobile from '../../components/post-mobile';
|
import PostMobile from '../../components/post-mobile';
|
||||||
import SubplebbitDescription from '../../components/subplebbit-description';
|
|
||||||
import SubplebbitRules from '../../components/subplebbit-rules';
|
|
||||||
import styles from './post.module.css';
|
import styles from './post.module.css';
|
||||||
|
|
||||||
export interface PostProps {
|
export interface PostProps {
|
||||||
@@ -57,8 +55,6 @@ const PostPage = () => {
|
|||||||
const { commentCid } = params;
|
const { commentCid } = params;
|
||||||
const subplebbitAddress = useResolvedSubplebbitAddress();
|
const subplebbitAddress = useResolvedSubplebbitAddress();
|
||||||
const isInAllView = isAllView(location.pathname);
|
const isInAllView = isAllView(location.pathname);
|
||||||
const isInDescriptionView = isDescriptionView(location.pathname, params);
|
|
||||||
const isInRulesView = isRulesView(location.pathname, params);
|
|
||||||
|
|
||||||
const comment = useComment({ commentCid });
|
const comment = useComment({ commentCid });
|
||||||
const subplebbit = useSubplebbit({ subplebbitAddress });
|
const subplebbit = useSubplebbit({ subplebbitAddress });
|
||||||
@@ -98,21 +94,7 @@ const PostPage = () => {
|
|||||||
<ErrorDisplay error={error} />
|
<ErrorDisplay error={error} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{isInDescriptionView ? (
|
<Post post={post} showAllReplies={true} />
|
||||||
<SubplebbitDescription
|
|
||||||
avatarUrl={suggested?.avatarUrl}
|
|
||||||
createdAt={createdAt}
|
|
||||||
description={description}
|
|
||||||
replyCount={location.pathname.startsWith('/all/') ? 0 : rules?.length > 0 ? 1 : 0}
|
|
||||||
subplebbitAddress={subplebbitAddress}
|
|
||||||
shortAddress={shortAddress}
|
|
||||||
title={title}
|
|
||||||
/>
|
|
||||||
) : isInRulesView ? (
|
|
||||||
<SubplebbitRules createdAt={createdAt} rules={rules} subplebbitAddress={subplebbitAddress} />
|
|
||||||
) : (
|
|
||||||
<Post post={post} showAllReplies={true} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user