Merge pull request #505 from plebbit/development

Development
This commit is contained in:
Tom (plebeius.eth)
2024-08-30 12:50:10 +02:00
committed by GitHub
18 changed files with 234 additions and 118 deletions
+7 -5
View File
@@ -1,7 +1,10 @@
import { useEffect, useState } from 'react';
import { Outlet, Route, Routes, useLocation, useParams } from 'react-router-dom';
import { useAccountComment } from '@plebbit/plebbit-react-hooks';
import { isAllView, isSubscriptionsView } from './lib/utils/view-utils';
import useIsMobile from './hooks/use-is-mobile';
import useTheme from './hooks/use-theme';
import { timeFilterNames } from './hooks/use-time-filter';
import styles from './app.module.css';
import Board from './views/board';
import Catalog from './views/catalog';
@@ -16,8 +19,6 @@ import ChallengeModal from './components/challenge-modal';
import PostForm from './components/post-form';
import SubplebbitStats from './components/subplebbit-stats';
import TopBar from './components/topbar';
import { timeFilterNames } from './hooks/use-time-filter';
import useTheme from './hooks/use-theme';
const BoardLayout = () => {
const { accountCommentIndex, subplebbitAddress, timeFilterName } = useParams();
@@ -25,8 +26,9 @@ const BoardLayout = () => {
const isMobile = useIsMobile();
const isInAllView = isAllView(location.pathname, useParams());
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
const pendingPost = useAccountComment({ commentIndex: accountCommentIndex ? parseInt(accountCommentIndex) : undefined });
const isValidAccountCommentIndex = !accountCommentIndex || (!isNaN(parseInt(accountCommentIndex)) && parseInt(accountCommentIndex) >= 0);
const isValidAccountCommentIndex = !accountCommentIndex || (!isNaN(Number(accountCommentIndex)) && Number(accountCommentIndex) >= 0);
if (!isValidAccountCommentIndex || (timeFilterName && !timeFilterNames.includes(timeFilterName))) {
return <NotFound />;
@@ -42,13 +44,13 @@ const BoardLayout = () => {
<TopBar />
<BoardHeader />
{isMobile
? (subplebbitAddress || isInAllView || isInSubscriptionsView) && (
? (subplebbitAddress || isInAllView || isInSubscriptionsView || pendingPost?.subplebbitAddress) && (
<>
<PostForm key={key} />
<MobileBoardButtons />
</>
)
: (subplebbitAddress || isInAllView || isInSubscriptionsView) && (
: (subplebbitAddress || isInAllView || isInSubscriptionsView || pendingPost?.subplebbitAddress) && (
<>
<PostForm key={key} />
{!(isInAllView || isInSubscriptionsView) && <SubplebbitStats />}
@@ -267,11 +267,9 @@ export const DesktopBoardButtons = () => {
<ReturnButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} />
] [
<CatalogButton address={subplebbitAddress} isInAllView={isInAllView} isInSubscriptionsView={isInSubscriptionsView} />]
{isInPostView && (
<span className={styles.rightSideButtons}>
<PostPageStats />
</span>
)}
<span className={styles.rightSideButtons}>
<PostPageStats />
</span>
</>
) : (
<>
+15 -10
View File
@@ -1,10 +1,9 @@
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { Trans, useTranslation } from 'react-i18next';
import { autoUpdate, flip, FloatingFocusManager, offset, shift, useClick, useDismiss, useFloating, useId, useInteractions, useRole } from '@floating-ui/react';
import { autoUpdate, FloatingFocusManager, offset, shift, useClick, useDismiss, useFloating, useId, useInteractions, useRole } from '@floating-ui/react';
import { Comment, PublishCommentEditOptions, usePublishCommentEdit } from '@plebbit/plebbit-react-hooks';
import styles from './edit-menu.module.css';
import { alertChallengeVerificationFailed } from '../../lib/utils/challenge-utils';
import { formatMarkdown } from '../../lib/utils/post-utils';
import useChallengesStore from '../../stores/use-challenges-store';
import _ from 'lodash';
import useIsMobile from '../../hooks/use-is-mobile';
@@ -90,7 +89,7 @@ const EditMenu = ({ isAccountMod, isAccountCommentAuthor, isCommentAuthorMod, po
placement: 'bottom-start',
open: isEditMenuOpen,
onOpenChange: setIsEditMenuOpen,
middleware: [offset(2), flip({ fallbackAxisSideDirection: 'end' }), shift()],
middleware: [offset(2), shift()],
whileElementsMounted: autoUpdate,
});
@@ -102,6 +101,13 @@ const EditMenu = ({ isAccountMod, isAccountCommentAuthor, isCommentAuthorMod, po
const headingId = useId();
useEffect(() => {
setPublishCommentEditOptions((prevOptions) => ({
...prevOptions,
commentCid: cid,
}));
}, [cid]);
const _publishCommentEdit = async () => {
try {
await publishCommentEdit();
@@ -114,11 +120,6 @@ const EditMenu = ({ isAccountMod, isAccountCommentAuthor, isCommentAuthorMod, po
setIsEditMenuOpen(false);
};
const handleContentChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
const formattedContent = formatMarkdown(e.target.value);
setPublishCommentEditOptions((state) => ({ ...state, content: formattedContent }));
};
return (
<>
<span className={`${styles.checkbox} ${isReply && styles.replyCheckbox}`} ref={refs.setReference} {...(cid && getReferenceProps())}>
@@ -146,7 +147,11 @@ const EditMenu = ({ isAccountMod, isAccountCommentAuthor, isCommentAuthorMod, po
</div>
{isContentEditorOpen && (
<div>
<textarea className={styles.editTextarea} defaultValue={publishCommentEditOptions.content ?? ''} onChange={handleContentChange} />
<textarea
className={styles.editTextarea}
defaultValue={publishCommentEditOptions.content ?? ''}
onChange={(e) => setPublishCommentEditOptions((state) => ({ ...state, content: e.target.value }))}
/>
</div>
)}
</>
+21 -23
View File
@@ -153,25 +153,22 @@ const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }:
<Link to={`/p/${subplebbitAddress}`}>p/{subplebbitAddress && Plebbit.getShortAddress(subplebbitAddress)}</Link>{' '}
</span>
)}
{!(isDescription || isRules) && (
<span className={styles.postNumLink}>
{cid ? (
<>
<Link to={`/p/${subplebbitAddress}/c/${cid}`} className={styles.linkToPost} title={t('link_to_post')} onClick={(e) => !cid && e.preventDefault()}>
c/
</Link>
<span className={styles.replyToPost} title={t('reply_to_post')} onMouseDown={() => openReplyModal && openReplyModal(cid, postCid, subplebbitAddress)}>
{shortCid}
</span>
</>
) : (
<>
<span style={{ cursor: 'pointer' }}>c/</span>
<span className={styles.pendingCid}>{state === 'failed' || stateString === 'Failed' ? 'Failed' : 'Pending'}</span>
</>
)}
</span>
)}
{!(isDescription || isRules) &&
(cid ? (
<span className={styles.postNumLink}>
<Link to={`/p/${subplebbitAddress}/c/${cid}`} className={styles.linkToPost} title={t('link_to_post')} onClick={(e) => !cid && e.preventDefault()}>
c/
</Link>
<span className={styles.replyToPost} title={t('reply_to_post')} onMouseDown={() => openReplyModal && openReplyModal(cid, postCid, subplebbitAddress)}>
{shortCid}
</span>
</span>
) : (
<>
<span>c/</span>
<span className={styles.pendingCid}>{state === 'failed' || stateString === 'Failed' ? 'Failed' : 'Pending'}</span>
</>
))}
{pinned && (
<span className={`${styles.stickyIconWrapper} ${!locked && styles.addPaddingBeforeReply}`}>
<img src='assets/icons/sticky.gif' alt='' className={styles.stickyIcon} title={t('sticky')} />
@@ -187,6 +184,7 @@ const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }:
[
<Link
to={isInAllView && isDescription ? '/p/all/description' : `/p/${subplebbitAddress}/${isDescription ? 'description' : isRules ? 'rules' : `c/${postCid}`}`}
onClick={(e) => !cid && e.preventDefault()}
>
{_.capitalize(t('reply'))}
</Link>
@@ -376,7 +374,7 @@ const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => {
post = editedComment;
}
const { author, cid, link, postCid, subplebbitAddress } = post || {};
const { author, cid, deleted, link, postCid, removed, subplebbitAddress } = post || {};
const isRouteLinkToReply = useLocation().pathname.startsWith(`/p/${subplebbitAddress}/c/${cid}`);
const { hidden } = useHide({ cid });
@@ -390,7 +388,7 @@ const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => {
data-post-cid={postCid}
>
<PostInfo openReplyModal={openReplyModal} post={post} postReplyCount={postReplyCount} roles={roles} />
{link && !hidden && isValidURL(link) && <PostMedia post={post} />}
{link && !hidden && !(deleted || removed) && isValidURL(link) && <PostMedia post={post} />}
{!hidden && <PostMessage post={post} />}
</div>
</div>
@@ -399,7 +397,7 @@ const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => {
const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies = true }: PostProps) => {
const { t } = useTranslation();
const { author, cid, content, link, pinned, postCid, state, subplebbitAddress } = post || {};
const { author, cid, content, deleted, link, pinned, postCid, removed, state, subplebbitAddress } = post || {};
const { isDescription, isRules } = post || {}; // custom properties, not from api
const params = useParams();
const location = useLocation();
@@ -445,7 +443,7 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
</span>
)}
<div data-cid={cid} data-author-address={author?.shortAddress} data-post-cid={postCid}>
{link && !isHidden && isValidURL(link) && <PostMedia post={post} />}
{link && !isHidden && !(deleted || removed) && isValidURL(link) && <PostMedia post={post} />}
<PostInfo isHidden={isHidden} openReplyModal={openReplyModal} post={post} postReplyCount={replyCount} roles={roles} />
{!isHidden && !content && <div className={styles.spacer} />}
{!isHidden && content && <PostMessage post={post} />}
@@ -165,6 +165,12 @@ const PostMenuDesktop = ({ post }: { post: Comment }) => {
const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss, role]);
const headingId = useId();
const handleMenuClick = () => {
if (cid) {
setMenuBtnRotated((prev) => !prev);
}
};
const handleClose = () => setMenuBtnRotated(false);
return (
@@ -173,13 +179,14 @@ const PostMenuDesktop = ({ post }: { post: Comment }) => {
<span
className={isInCatalogView ? styles.postMenuBtnCatalog : styles.postMenuBtn}
title='Post menu'
onClick={() => setMenuBtnRotated((prev) => !prev)}
style={{ transform: menuBtnRotated ? 'rotate(90deg)' : 'rotate(0deg)' }}
onClick={handleMenuClick}
style={{ transform: menuBtnRotated && cid ? 'rotate(90deg)' : 'rotate(0deg)' }}
>
▶
</span>
</span>
{menuBtnRotated &&
cid &&
createPortal(
<FloatingFocusManager context={context} modal={false}>
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
+10 -6
View File
@@ -125,7 +125,7 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
const author = account?.author || {};
const { displayName } = author || {};
const [url, setUrl] = useState('');
const { title, content, link, publishCommentOptions, setSubmitStore, resetSubmitStore } = useSubmitStore();
const { link, publishCommentOptions, setSubmitStore, resetSubmitStore } = useSubmitStore();
const { index, publishComment } = usePublishComment(publishCommentOptions);
const textRef = useRef<HTMLTextAreaElement>(null);
@@ -180,8 +180,12 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
}
}, [anonMode, getNewSigner, account, setSubmitStore, displayName]);
const onPublishPost = async () => {
if (!title && !content && !link) {
const onPublishPost = () => {
const currentTitle = subjectRef.current?.value.trim() || '';
const currentContent = textRef.current?.value.trim() || '';
const currentUrl = urlRef.current?.value.trim() || '';
if (!currentTitle && !currentContent && !currentUrl) {
alert(`Cannot post empty comment`);
return;
}
@@ -248,10 +252,10 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
};
const onPublishReply = () => {
const currentContent = textRef.current?.value || '';
const currentUrl = urlRef.current?.value || '';
const currentContent = textRef.current?.value.trim() || '';
const currentUrl = urlRef.current?.value.trim() || '';
if (!currentContent.trim() && !currentUrl) {
if (!currentContent && !currentUrl) {
alert(`Cannot post empty comment`);
return;
}
@@ -148,16 +148,23 @@ const PostMenuMobile = ({ post }: { post: Comment }) => {
const { getReferenceProps, getFloatingProps } = useInteractions([click, dismiss, role]);
const headingId = useId();
const handleMenuClick = () => {
if (cid) {
setIsMenuOpen((prev) => !prev);
}
};
const handleClose = () => setIsMenuOpen(false);
const isInBoardView = isBoardView(useLocation().pathname, useParams());
return (
<>
<span className={styles.postMenuBtn} title='Post menu' onClick={() => setIsMenuOpen((prev) => !prev)} ref={refs.setReference} {...getReferenceProps()}>
<span className={styles.postMenuBtn} title='Post menu' onClick={handleMenuClick} ref={refs.setReference} {...getReferenceProps()}>
...
</span>
{isMenuOpen &&
cid &&
createPortal(
<FloatingFocusManager context={context} modal={false}>
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
@@ -171,7 +178,7 @@ const PostMenuMobile = ({ post }: { post: Comment }) => {
</FloatingFocusManager>,
document.body,
)}
{(isAccountMod || isAccountCommentAuthor) && (
{(isAccountMod || isAccountCommentAuthor) && cid && (
<span className={styles.checkbox}>
<EditMenu isAccountCommentAuthor={isAccountCommentAuthor} isAccountMod={isAccountMod} isCommentAuthorMod={isCommentAuthorMod} post={post} />
</span>
+19 -20
View File
@@ -141,28 +141,27 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P
</div>
)}
<Tooltip children={<span>{getFormattedDate(timestamp)}</span>} content={getFormattedTimeAgo(timestamp)} />{' '}
{!(isDescription || isRules) && (
<span className={styles.postNumLink}>
{cid ? (
<>
<Link to={`/p/${subplebbitAddress}/c/${cid}`} className={styles.linkToPost} title={t('link_to_post')} onClick={(e) => !cid && e.preventDefault()}>
c/
</Link>
<span className={styles.replyToPost} title={t('reply_to_post')} onMouseDown={() => openReplyModal && openReplyModal(cid, postCid, subplebbitAddress)}>
{shortCid.slice(0, -4)}
</span>
</>
) : (
<>
<span style={{ cursor: 'pointer' }}>c/</span>
<span className={styles.pendingCid}>{state === 'failed' || stateString === 'Failed' ? 'Failed' : 'Pending'}</span>
</>
)}
</span>
)}
{!(isDescription || isRules) &&
(cid ? (
<span className={styles.postNumLink}>
<Link to={`/p/${subplebbitAddress}/c/${cid}`} className={styles.linkToPost} title={t('link_to_post')} onClick={(e) => !cid && e.preventDefault()}>
c/
</Link>
<span className={styles.replyToPost} title={t('reply_to_post')} onMouseDown={() => openReplyModal && openReplyModal(cid, postCid, subplebbitAddress)}>
{shortCid.slice(0, -4)}
</span>
</span>
) : (
<>
<span>c/</span>
<span className={styles.pendingCid}>{state === 'failed' || stateString === 'Failed' ? 'Failed' : 'Pending'}</span>
</>
))}
</span>
</div>
{(hasThumbnail || link) && <CommentMedia commentMediaInfo={commentMediaInfo} post={post} showThumbnail={showThumbnail} setShowThumbnail={setShowThumbnail} />}
{(hasThumbnail || link) && !(deleted || removed) && (
<CommentMedia commentMediaInfo={commentMediaInfo} post={post} showThumbnail={showThumbnail} setShowThumbnail={setShowThumbnail} />
)}
</>
);
};
+3 -3
View File
@@ -66,10 +66,10 @@ const ReplyModal = ({ closeModal, showReplyModal, parentCid, postCid, scrollY, s
}, [address, getExistingSigner, getNewSigner, setPublishReplyOptions, anonMode, displayName]);
const onPublishReply = () => {
const currentContent = textRef.current?.value || '';
const currentUrl = urlRef.current?.value || '';
const currentContent = textRef.current?.value.slice(contentPrefix.length).trim() || '';
const currentUrl = urlRef.current?.value.trim() || '';
if (!currentContent.trim() && !currentUrl) {
if (!currentContent && !currentUrl) {
alert(`Cannot post empty comment`);
return;
}
@@ -1,9 +1,9 @@
import { useState } from 'react';
import { useLocation, useParams } from 'react-router-dom';
import { useAccountComment, useComment, useSubplebbit, useSubplebbitStats } from '@plebbit/plebbit-react-hooks';
import styles from './subplebbit-stats.module.css';
import { Trans, useTranslation } from 'react-i18next';
import { useAccountComment, useComment, useSubplebbit, useSubplebbitStats } from '@plebbit/plebbit-react-hooks';
import useSubplebbitStatsVisibilityStore from '../../stores/use-subplebbit-stats-visibility-store';
import { isDescriptionView, isRulesView } from '../../lib/utils/view-utils';
import styles from './subplebbit-stats.module.css';
const SubplebbitStats = () => {
const { t } = useTranslation();
@@ -16,7 +16,8 @@ const SubplebbitStats = () => {
const { address, createdAt } = subplebbit || {};
const stats = useSubplebbitStats({ subplebbitAddress: address });
const [showStats, setShowStats] = useState(true);
const { hiddenStats, toggleVisibility } = useSubplebbitStatsVisibilityStore();
const isHidden = hiddenStats[address];
const location = useLocation();
const isInDescriptionView = isDescriptionView(location.pathname, params);
@@ -44,7 +45,7 @@ const SubplebbitStats = () => {
</td>
</tr>
</thead>
{showStats && (
{!isHidden && (
<tbody>
<tr>
<td>
@@ -93,8 +94,8 @@ const SubplebbitStats = () => {
<tr>
<td colSpan={2}>
[
<span className={styles.hideButton} onClick={() => setShowStats(!showStats)}>
{showStats ? t('hide') : t('show_stats')}
<span className={styles.hideButton} onClick={() => toggleVisibility(address)}>
{isHidden ? t('show_stats') : t('hide')}
</span>
]
</td>
+31 -6
View File
@@ -4,10 +4,13 @@ import useThemeStore from '../stores/use-theme-store';
import useDefaultSubplebbits from './use-default-subplebbits';
import { isAllView, isHomeView, isNotFoundView, isPendingPostView, isSubscriptionsView } from '../lib/utils/view-utils';
import { nsfwTags } from '../views/home/home';
import { useAccountComment } from '@plebbit/plebbit-react-hooks';
const useInitialTheme = () => {
const useInitialTheme = (pendingPostSubplebbitAddress?: string) => {
const location = useLocation();
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
const { subplebbitAddress: paramsSubplebbitAddress, accountCommentIndex } = useParams<{ subplebbitAddress: string; accountCommentIndex?: string }>();
const commentIndex = accountCommentIndex ? parseInt(accountCommentIndex) : undefined;
const pendingPost = useAccountComment({ commentIndex });
const getTheme = useThemeStore((state) => state.getTheme);
const currentTheme = useThemeStore((state) => state.currentTheme);
const subplebbits = useDefaultSubplebbits();
@@ -22,13 +25,23 @@ const useInitialTheme = () => {
let theme = 'yotsuba';
if (isInPendingPostView) {
theme = currentTheme || 'yotsuba';
const subplebbitAddress = pendingPostSubplebbitAddress || pendingPost?.subplebbitAddress;
if (subplebbitAddress) {
const subplebbit = subplebbits.find((s) => s.address === subplebbitAddress);
if (subplebbit && subplebbit.tags && subplebbit.tags.some((tag) => nsfwTags.includes(tag))) {
theme = getTheme('nsfw', false) || 'yotsuba';
} else {
theme = getTheme('sfw', false) || 'yotsuba-b';
}
} else {
theme = currentTheme || 'yotsuba';
}
} else if (isInAllView || isInSubscriptionsView) {
theme = getTheme('sfw', false) || 'yotsuba-b'; // Add 'false' parameter
} else if (isInHomeView || isInNotFoundView) {
theme = 'yotsuba';
} else if (subplebbitAddress) {
const subplebbit = subplebbits.find((s) => s.address === subplebbitAddress);
} else if (paramsSubplebbitAddress) {
const subplebbit = subplebbits.find((s) => s.address === paramsSubplebbitAddress);
if (subplebbit && subplebbit.tags && subplebbit.tags.some((tag) => nsfwTags.includes(tag))) {
theme = getTheme('nsfw', false) || 'yotsuba'; // Add 'false' parameter
} else {
@@ -37,7 +50,19 @@ const useInitialTheme = () => {
}
return theme;
}, [isInPendingPostView, isInAllView, isInSubscriptionsView, isInHomeView, isInNotFoundView, subplebbitAddress, getTheme, currentTheme, subplebbits]);
}, [
isInPendingPostView,
isInAllView,
isInSubscriptionsView,
isInHomeView,
isInNotFoundView,
paramsSubplebbitAddress,
getTheme,
currentTheme,
subplebbits,
pendingPostSubplebbitAddress,
pendingPost,
]);
return initialTheme;
};
+18 -6
View File
@@ -87,7 +87,7 @@ const useReplyStore = create<ReplyState>((set) => ({
const useReply = ({ cid, subplebbitAddress }: { cid: string; subplebbitAddress: string }) => {
const parentCid = cid;
const { author, displayName, signer, content, link, spoiler, publishCommentOptions } = useReplyStore((state) => ({
const { author, signer, content, link, spoiler, publishCommentOptions } = useReplyStore((state) => ({
author: state.author[parentCid],
displayName: state.displayName[parentCid],
signer: state.signer[parentCid],
@@ -107,17 +107,29 @@ const useReply = ({ cid, subplebbitAddress }: { cid: string; subplebbitAddress:
const newOptions: Partial<SetReplyStoreData> = {
subplebbitAddress,
parentCid,
content: options.content || content,
displayName: options.displayName || displayName,
link: options.link || link,
content: options.content ?? content,
link: options.link ?? link,
spoiler: options.spoiler ?? spoiler,
author: anonMode ? signer?.author || options.author || author || { displayName } : undefined,
signer: anonMode ? signer || options.signer : undefined,
};
if ('displayName' in options) {
newOptions.displayName = options.displayName;
}
if (anonMode) {
const currentAuthor = signer?.author || author || {};
newOptions.author = {
...currentAuthor,
...('displayName' in options && { displayName: options.displayName }),
};
} else {
newOptions.author = undefined;
}
setReplyStore(newOptions as SetReplyStoreData);
},
[subplebbitAddress, parentCid, author, displayName, signer, content, link, spoiler, setReplyStore, anonMode],
[subplebbitAddress, parentCid, author, signer, content, link, spoiler, setReplyStore, anonMode],
);
const resetPublishReplyOptions = useCallback(() => resetReplyStore(parentCid), [parentCid, resetReplyStore]);
+31 -10
View File
@@ -1,4 +1,4 @@
import { useMemo, useCallback } from 'react';
import { useMemo, useCallback, useState, useEffect } from 'react';
import { Comment, useAccountComments } from '@plebbit/plebbit-react-hooks';
import { flattenCommentsPages } from '@plebbit/plebbit-react-hooks/dist/lib/utils';
@@ -9,24 +9,45 @@ const useReplies = (comment: Comment) => {
// generate a Set of CIDs from flattened replies for quick lookup
const replyCids = useMemo(() => new Set(flattenedReplies.map((reply) => reply?.cid)), [flattenedReplies]);
// filter against the original comment's CID and all CIDs in flattened replies
const filter = useCallback(
(accountComment: Comment) => {
const parentCid = accountComment.parentCid;
return parentCid === (comment?.cid || 'n/a') || replyCids.has(parentCid);
const [filteredAccountComments, setFilteredAccountComments] = useState<Comment[]>([]);
const getPostCid = useCallback(
(accountComment: Comment, allComments: Comment[]): string | null => {
if (accountComment.parentCid === comment?.cid) {
return comment?.cid;
}
const parent = allComments.find((c) => c.cid === accountComment.parentCid);
if (!parent) {
return null;
}
return getPostCid(parent, allComments);
},
[comment?.cid, replyCids],
[comment?.cid],
);
const { accountComments } = useAccountComments({ filter });
const { accountComments } = useAccountComments();
useEffect(() => {
const filterComments = (comments: Comment[]) => {
return comments.filter((accountComment) => {
const parentCid = accountComment.parentCid;
if (parentCid === (comment?.cid || 'n/a') || replyCids.has(parentCid)) {
return true;
}
return getPostCid(accountComment, comments) === comment?.cid;
});
};
setFilteredAccountComments(filterComments(accountComments));
}, [accountComments, comment?.cid, replyCids, getPostCid]);
// the account's replies have a delay before getting published, so get them locally from accountComments instead
const accountRepliesNotYetPublished = useMemo(() => {
const replies = flattenedReplies || [];
const replyCids = new Set(replies.map((reply: Comment) => reply?.cid));
// filter out the account comments already in comment.replies, so they don't appear twice
return accountComments.filter((accountReply) => !replyCids.has(accountReply?.cid));
}, [flattenedReplies, accountComments]);
return filteredAccountComments.filter((accountReply) => !replyCids.has(accountReply?.cid));
}, [flattenedReplies, filteredAccountComments]);
const repliesAndNotYetPublishedReplies = useMemo(() => {
const repliesSortedByPinnedAndTimestamp = [...accountRepliesNotYetPublished.reverse(), ...(flattenedReplies || [])];
+2 -2
View File
@@ -15,7 +15,7 @@ const updateThemeClass = (newTheme: string) => {
}
};
const useTheme = (): [string, (theme: string) => void] => {
const useTheme = (pendingPostSubplebbitAddress?: string): [string, (theme: string) => void] => {
const location = useLocation();
const params = useParams<{ subplebbitAddress: string }>();
const setThemeStore = useThemeStore((state) => state.setTheme);
@@ -23,7 +23,7 @@ const useTheme = (): [string, (theme: string) => void] => {
const loadThemes = useThemeStore((state) => state.loadThemes);
const subplebbits = useDefaultSubplebbits();
const initialTheme = useInitialTheme();
const initialTheme = useInitialTheme(pendingPostSubplebbitAddress);
const [currentTheme, setCurrentTheme] = useState(initialTheme);
const getCurrentTheme = useCallback(() => {
+10 -4
View File
@@ -24,10 +24,16 @@ export function getTextColorForBackground(rgb: string): string {
export const formatMarkdown = (content: string): string => {
let md = content;
if (md) {
// Replace single newline with "/n&nbsp;/n" if followed by a newline
md = md.replace(/\n(?=\n)/g, '\n&nbsp;\n');
// Replace single newline with double newline if between two characters
md = md.replace(/\n(?=\S)/g, '\n\n');
// Check if the content already contains valid Markdown patterns
const alreadyFormattedPattern = /\n&nbsp;\n/;
// help the user by formatting the content if it's not already formatted
if (!alreadyFormattedPattern.test(md)) {
// Replace single newline with "/n&nbsp;/n" if followed by a newline
md = md.replace(/\n(?=\n)/g, '\n&nbsp;\n');
// Replace single newline with double newline if between two characters
md = md.replace(/\n(?=\S)/g, '\n\n');
}
}
return md;
};
@@ -0,0 +1,27 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface SubplebbitStatsVisibilityState {
hiddenStats: { [subplebbitAddress: string]: boolean };
toggleVisibility: (subplebbitAddress: string) => void;
}
const useSubplebbitStatsVisibilityStore = create<SubplebbitStatsVisibilityState>()(
persist(
(set) => ({
hiddenStats: {},
toggleVisibility: (subplebbitAddress) =>
set((state) => ({
hiddenStats: {
...state.hiddenStats,
[subplebbitAddress]: !state.hiddenStats[subplebbitAddress],
},
})),
}),
{
name: 'subplebbit-stats-visibility',
},
),
);
export default useSubplebbitStatsVisibilityStore;
+7 -6
View File
@@ -9,11 +9,10 @@ import { nsfwTags } from '../home';
import BoxModal from '../box-modal';
import useIsSubplebbitOffline from '../../../hooks/use-is-subplebbit-offline';
const Board = ({ subplebbit, subplebbits }: { subplebbit: Subplebbit; subplebbits: any }) => {
const Board = ({ subplebbit, subplebbits, useCatalog }: { subplebbit: Subplebbit; subplebbits: any; useCatalog?: boolean }) => {
const { t } = useTranslation();
const { address, title, tags } = subplebbit || {};
const nsfwTag = tags.find((tag: string) => nsfwTags.includes(tag));
const { useCatalog } = useHomeFiltersStore();
const boardLink = useCatalog ? `/p/${address}/catalog` : `/p/${address}`;
@@ -37,6 +36,8 @@ const BoardsBox = ({ multisub, subplebbits }: { multisub: Subplebbit[]; subplebb
const accountSubplebbitAddresses = Object.keys(accountSubplebbits);
const { showNsfwBoardsOnly, showWorksafeBoardsOnly } = useHomeFiltersStore();
const { useCatalog } = useHomeFiltersStore();
const filterSubs = (subs: Subplebbit[], includeTags: string[], excludeTags: string[] = []) =>
subs.filter((sub) => includeTags.every((tag) => sub.tags?.includes(tag)) && excludeTags.every((tag) => !sub.tags?.includes(tag)));
@@ -72,10 +73,10 @@ const BoardsBox = ({ multisub, subplebbits }: { multisub: Subplebbit[]; subplebb
<h3>Multiboards</h3>
<div className={styles.list}>
<div className={styles.subplebbit}>
<Link to='/p/all'>{multisubMetadata?.title || 'All'}</Link>
<Link to={useCatalog ? '/p/all/catalog' : '/p/all'}>{multisubMetadata?.title || 'All'}</Link>
</div>
<div className={styles.subplebbit}>
<Link to='/p/subscriptions'>Subscriptions</Link>
<Link to={useCatalog ? '/p/subscriptions/catalog' : '/p/subscriptions'}>Subscriptions</Link>
</div>
</div>
{plebbitSubs.length > 0 && (
@@ -139,7 +140,7 @@ const BoardsBox = ({ multisub, subplebbits }: { multisub: Subplebbit[]; subplebb
{subscriptions.length > 0
? subscriptions.map((address: string, index: number) => (
<div className={styles.subplebbit} key={index}>
<Link to={`/p/${address}`}>p/{address && Plebbit.getShortAddress(address)}</Link>
<Link to={useCatalog ? `/p/${address}/catalog` : `/p/${address}`}>p/{address && Plebbit.getShortAddress(address)}</Link>
</div>
))
: t('not_subscribed')}
@@ -149,7 +150,7 @@ const BoardsBox = ({ multisub, subplebbits }: { multisub: Subplebbit[]; subplebb
{accountSubplebbitAddresses.length > 0
? accountSubplebbitAddresses.map((address: string, index: number) => (
<div className={styles.subplebbit} key={index}>
<Link to={`/p/${address}`}>p/{address && Plebbit.getShortAddress(address)}</Link>
<Link to={useCatalog ? `/p/${address}/catalog` : `/p/${address}`}>p/{address && Plebbit.getShortAddress(address)}</Link>
</div>
))
: t('not_moderating')}
+4 -1
View File
@@ -4,6 +4,7 @@ import { useAccountComment } from '@plebbit/plebbit-react-hooks';
import { isSettingsView } from '../../lib/utils/view-utils';
import { Post } from '../post';
import SettingsModal from '../../components/settings-modal';
import useTheme from '../../hooks/use-theme';
const PendingPost = () => {
const { accountCommentIndex } = useParams<{ accountCommentIndex?: string }>();
@@ -14,6 +15,8 @@ const PendingPost = () => {
const params = useParams();
const isInSettingsView = isSettingsView(location.pathname, params);
useTheme(post?.subplebbitAddress);
useEffect(() => window.scrollTo(0, 0), []);
useEffect(() => {
@@ -25,7 +28,7 @@ const PendingPost = () => {
return (
<>
{isInSettingsView && <SettingsModal />}
<Post post={post} showReplies={false} />
<Post post={post} />
</>
);
};