mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
+7
-5
@@ -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>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
</>
|
||||
|
||||
@@ -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()}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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
@@ -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 || [])];
|
||||
|
||||
@@ -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(() => {
|
||||
|
||||
@@ -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 /n" if followed by a newline
|
||||
md = md.replace(/\n(?=\n)/g, '\n \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 \n/;
|
||||
|
||||
// help the user by formatting the content if it's not already formatted
|
||||
if (!alreadyFormattedPattern.test(md)) {
|
||||
// Replace single newline with "/n /n" if followed by a newline
|
||||
md = md.replace(/\n(?=\n)/g, '\n \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;
|
||||
@@ -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,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} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user