mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
fix(codebase audit): preserve cleanup without regressions
Fix codebase audit regressions while preserving UI/UX behavior and adding review-driven hardening.
This commit is contained in:
@@ -34,6 +34,7 @@ import LoadingEllipsis from '../loading-ellipsis';
|
||||
import PostMenuDesktop from './post-menu-desktop';
|
||||
import ReplyQuotePreview from '../reply-quote-preview';
|
||||
import Tooltip from '../tooltip';
|
||||
import TimeAgoTooltip from '../time-ago-tooltip';
|
||||
import { PostProps } from '../../views/post/post';
|
||||
import { create } from 'zustand';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
@@ -244,8 +245,8 @@ const PostInfo = ({
|
||||
const location = useLocation();
|
||||
const isInPostPageView = isPostPageView(location.pathname, params);
|
||||
const isInModQueueView = isModQueueView(location.pathname);
|
||||
const { getAlertThresholdSeconds } = useModQueueStore();
|
||||
const currentTime = useCurrentTime();
|
||||
const getAlertThresholdSeconds = useModQueueStore((state) => state.getAlertThresholdSeconds);
|
||||
const currentTime = useCurrentTime(isInModQueueView ? 60 : false);
|
||||
const account = useAccount();
|
||||
const accountAddress = account?.author?.address;
|
||||
|
||||
@@ -282,22 +283,26 @@ const PostInfo = ({
|
||||
return Math.max(postsByAuthorInThread?.get(shortAddress) ?? 0, 1);
|
||||
})();
|
||||
|
||||
const { hidden } = useHide(post);
|
||||
const { hidden } = useHide({ cid: cid || '' });
|
||||
|
||||
const { openReplyModal } = useReplyModalStore();
|
||||
|
||||
const onReplyModalClick = () => {
|
||||
deleted
|
||||
? isReply
|
||||
? alert(t('this_reply_was_deleted'))
|
||||
: alert(t('this_thread_was_deleted'))
|
||||
: removed || purged
|
||||
? isReply
|
||||
? alert(t('this_reply_was_removed'))
|
||||
: alert(t('this_thread_was_removed'))
|
||||
: archived && !isReply
|
||||
? alert(t('thread_archived'))
|
||||
: openReplyModal && openReplyModal(cid, post?.number, postCid, threadNumber, communityAddress);
|
||||
if (deleted) {
|
||||
alert(t(isReply ? 'this_reply_was_deleted' : 'this_thread_was_deleted'));
|
||||
return;
|
||||
}
|
||||
if (removed || purged) {
|
||||
alert(t(isReply ? 'this_reply_was_removed' : 'this_thread_was_removed'));
|
||||
return;
|
||||
}
|
||||
if (archived && !isReply) {
|
||||
alert(t('thread_archived'));
|
||||
return;
|
||||
}
|
||||
if (cid && postCid && communityAddress && openReplyModal) {
|
||||
openReplyModal(cid, post?.number, postCid, threadNumber, communityAddress);
|
||||
}
|
||||
};
|
||||
|
||||
const threadRoute = cid ? (boardPath ? `/${boardPath}/thread/${cid}` : `/thread/${cid}`) : undefined;
|
||||
@@ -329,7 +334,7 @@ const PostInfo = ({
|
||||
|
||||
return (
|
||||
<div className={styles.postInfo} data-post-info-cid={cid}>
|
||||
{isHidden ? parentCid && <span className={styles.hiddenReplyEditMenuSpacer} /> : <EditMenu post={post} />}
|
||||
{isHidden ? parentCid && <span className={styles.hiddenReplyEditMenuSpacer} /> : post ? <EditMenu post={post} /> : null}
|
||||
<span className={(hidden || ((removed || deleted || purged) && !reason)) && parentCid ? styles.postDesktopHidden : ''}>
|
||||
{title &&
|
||||
(title.length <= 75 ? (
|
||||
@@ -410,15 +415,15 @@ const PostInfo = ({
|
||||
<span className={styles.dateTime}>
|
||||
{isInModQueueView && isOverThreshold ? (
|
||||
<>
|
||||
<Tooltip content={getFormattedTimeAgo(timestamp)}>
|
||||
<TimeAgoTooltip timestamp={timestamp}>
|
||||
<span>{getFormattedDate(timestamp)}</span>
|
||||
</Tooltip>{' '}
|
||||
</TimeAgoTooltip>{' '}
|
||||
(<span className={styles.alert}>{getFormattedTimeAgo(timestamp)}</span>)
|
||||
</>
|
||||
) : (
|
||||
<Tooltip content={getFormattedTimeAgo(timestamp)}>
|
||||
<TimeAgoTooltip timestamp={timestamp}>
|
||||
<span>{getFormattedDate(timestamp)}</span>
|
||||
</Tooltip>
|
||||
</TimeAgoTooltip>
|
||||
)}{' '}
|
||||
</span>
|
||||
<span className={styles.postNum}>
|
||||
@@ -529,7 +534,7 @@ const PostInfo = ({
|
||||
{shouldShowPendingApprovalButtons && communityAddress && cid && <PendingModerationActions cid={cid} communityAddress={communityAddress} post={post} />}
|
||||
</span>
|
||||
{!(removed || deleted || purged) && !isModQueue && <PostMenuDesktop postMenu={postMenuProps} />}
|
||||
{cid && parentCid && <ReplyBacklinks post={post} quotedByMap={quotedByMap} directRepliesByParentCid={directRepliesByParentCid} />}
|
||||
{post && cid && parentCid && <ReplyBacklinks post={post} quotedByMap={quotedByMap} directRepliesByParentCid={directRepliesByParentCid} />}
|
||||
{cid && !parentCid && <OpBacklinks cid={cid} quotedByMap={quotedByMap} />}
|
||||
</span>
|
||||
</div>
|
||||
@@ -589,13 +594,13 @@ const OpBacklinks = ({ cid, quotedByMap }: { cid: string; quotedByMap?: Map<stri
|
||||
interface PostMediaProps {
|
||||
commentMediaInfo: CommentMediaInfo | undefined;
|
||||
hasThumbnail: boolean;
|
||||
spoiler: boolean;
|
||||
deleted: boolean;
|
||||
spoiler?: boolean;
|
||||
deleted?: boolean;
|
||||
purged: boolean;
|
||||
removed: boolean;
|
||||
linkHeight: number;
|
||||
linkWidth: number;
|
||||
parentCid: string;
|
||||
removed?: boolean;
|
||||
linkHeight?: number;
|
||||
linkWidth?: number;
|
||||
parentCid?: string;
|
||||
communityAddress?: string;
|
||||
isInAllView: boolean;
|
||||
isInSubscriptionsView: boolean;
|
||||
@@ -659,7 +664,8 @@ const PostMedia = ({
|
||||
if (spoiler) return capitalize(t('spoiler'));
|
||||
if (requirePostLinkIsMedia && url) {
|
||||
try {
|
||||
const filename = new URL(url).pathname.split('/').pop();
|
||||
const pathParts = new URL(url).pathname.split('/');
|
||||
const filename = pathParts[pathParts.length - 1];
|
||||
if (filename && /\.\w+$/.test(filename)) return truncateWithEllipsisInMiddle(filename);
|
||||
} catch {}
|
||||
}
|
||||
@@ -809,7 +815,7 @@ const Reply = ({
|
||||
isInModView={isInModView}
|
||||
/>
|
||||
)}
|
||||
{!hidden && (!(removed || deleted || purged) || ((removed || deleted) && reason) || purged) && (
|
||||
{post && !hidden && (!(removed || deleted || purged) || ((removed || deleted) && reason) || purged) && (
|
||||
<CommentContent comment={post} prependContent={failedPublishNotice} />
|
||||
)}
|
||||
</div>
|
||||
@@ -836,8 +842,7 @@ const PostDesktop = ({
|
||||
}: PostProps) => {
|
||||
const { t } = useTranslation();
|
||||
const resolvedPost = withResolvedCommentCommunityAddress(post);
|
||||
const { author, cid, content, deleted, link, linkHeight, linkWidth, pinned, postCid, removed, spoiler, state, communityAddress, thumbnailUrl, parentCid } =
|
||||
resolvedPost || {};
|
||||
const { author, cid, content, deleted, link, linkHeight, linkWidth, postCid, removed, spoiler, state, communityAddress, thumbnailUrl, parentCid } = resolvedPost || {};
|
||||
const purged = resolvedPost?.commentModeration?.purged;
|
||||
const params = useParams();
|
||||
const location = useLocation();
|
||||
@@ -876,9 +881,7 @@ const PostDesktop = ({
|
||||
repliesPerPage: BOARD_REPLIES_PREVIEW_FETCH_SIZE,
|
||||
accountComments: { newerThan: Infinity, append: true },
|
||||
});
|
||||
const cachedPreviewReplies = (cachedPreviewRepliesResult as { updatedReplies?: Comment[] }).updatedReplies?.length
|
||||
? (cachedPreviewRepliesResult as { updatedReplies?: Comment[] }).updatedReplies!
|
||||
: cachedPreviewRepliesResult.replies || [];
|
||||
const cachedPreviewReplies = cachedPreviewRepliesResult.updatedReplies?.length ? cachedPreviewRepliesResult.updatedReplies : cachedPreviewRepliesResult.replies || [];
|
||||
const hasEnoughCachedPreview = hasEnoughPreviewReplies({
|
||||
replyCount: resolvedPost?.replyCount,
|
||||
loadedCount: cachedPreviewReplies.length,
|
||||
@@ -899,14 +902,12 @@ const PostDesktop = ({
|
||||
accountComments: { newerThan: Infinity, append: true },
|
||||
});
|
||||
|
||||
const livePreviewReplies = (previewRepliesResult as { updatedReplies?: Comment[] }).updatedReplies?.length
|
||||
? (previewRepliesResult as { updatedReplies?: Comment[] }).updatedReplies!
|
||||
: previewRepliesResult.replies || [];
|
||||
const livePreviewReplies = previewRepliesResult.updatedReplies?.length ? previewRepliesResult.updatedReplies : previewRepliesResult.replies || [];
|
||||
const previewReplies = hasReplyPaginationOverride ? replyPaginationOverride.replies : hasEnoughCachedPreview ? cachedPreviewReplies : livePreviewReplies;
|
||||
const fullReplies = hasReplyPaginationOverride
|
||||
? replyPaginationOverride.replies
|
||||
: (fullRepliesResult as { updatedReplies?: Comment[] }).updatedReplies?.length
|
||||
? (fullRepliesResult as { updatedReplies?: Comment[] }).updatedReplies!
|
||||
: fullRepliesResult.updatedReplies?.length
|
||||
? fullRepliesResult.updatedReplies
|
||||
: fullRepliesResult.replies || [];
|
||||
|
||||
const hasMore = replyPaginationOverride?.hasMore ?? fullRepliesResult.hasMore;
|
||||
@@ -1191,7 +1192,7 @@ const PostDesktop = ({
|
||||
directRepliesByParentCid={directRepliesByParentCid}
|
||||
/>
|
||||
{!isHidden && !content && !(deleted || removed || purged) && <div className={styles.spacer} />}
|
||||
{!isHidden && <CommentContent comment={resolvedPost} prependContent={failedPublishNotice} />}
|
||||
{resolvedPost && !isHidden && <CommentContent comment={resolvedPost} prependContent={failedPublishNotice} />}
|
||||
</div>
|
||||
{!isHidden && !isInPendingPostView && showReplies && repliesCount > 0 && !isInPostPageView && (
|
||||
<span className={styles.summary}>
|
||||
|
||||
@@ -215,9 +215,9 @@ describe('PostMenuDesktop', () => {
|
||||
const hrefs = Array.from(document.body.querySelectorAll('a')).map((link) => link.getAttribute('href'));
|
||||
expect(hrefs).toEqual(
|
||||
expect.arrayContaining([
|
||||
'https://lens.google.com/uploadbyurl?url=https://cdn.example/image.png',
|
||||
'https://www.yandex.com/images/search?url=https://cdn.example/image.png&rpt=imageview',
|
||||
'https://saucenao.com/search.php?url=https://cdn.example/image.png',
|
||||
'https://lens.google.com/uploadbyurl?url=https%3A%2F%2Fcdn.example%2Fimage.png',
|
||||
'https://www.yandex.com/images/search?img_url=https%3A%2F%2Fcdn.example%2Fimage.png&rpt=imageview',
|
||||
'https://saucenao.com/search.php?url=https%3A%2F%2Fcdn.example%2Fimage.png',
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -9,7 +9,7 @@ import { copyShareLinkToClipboard, isValidURL, type ShareLinkType } from '../../
|
||||
import { copyToClipboard } from '../../../lib/utils/clipboard-utils';
|
||||
import { getBoardPath } from '../../../lib/utils/route-utils';
|
||||
import { useDirectories } from '../../../hooks/use-directories';
|
||||
import { isAllView, isCatalogView, isPostPageView, isSubscriptionsView } from '../../../lib/utils/view-utils';
|
||||
import { isCatalogView, isPostPageView } from '../../../lib/utils/view-utils';
|
||||
import useHide from '../../../hooks/use-hide';
|
||||
import capitalize from 'lodash/capitalize';
|
||||
import { PostMenuProps } from '../../../lib/utils/post-menu-props';
|
||||
@@ -119,6 +119,7 @@ const CopyUserIdButton = ({ address, onClose }: { address: string; onClose: () =
|
||||
const ImageSearchButton = ({ url, onClose }: { url: string; onClose: () => void }) => {
|
||||
const { t } = useTranslation();
|
||||
const [isImageSearchMenuOpen, setIsImageSearchMenuOpen] = useState(false);
|
||||
const encodedUrl = encodeURIComponent(url);
|
||||
|
||||
const { refs, floatingStyles } = useFloating({
|
||||
placement: 'right-start',
|
||||
@@ -144,13 +145,13 @@ const ImageSearchButton = ({ url, onClose }: { url: string; onClose: () => void
|
||||
{capitalize(t('image_search'))} »
|
||||
{isImageSearchMenuOpen && (
|
||||
<div ref={refs.setFloating} style={floatingStyles} className={styles.dropdownMenu}>
|
||||
<a href={`https://lens.google.com/uploadbyurl?url=${url}`} target='_blank' rel='noreferrer'>
|
||||
<a href={`https://lens.google.com/uploadbyurl?url=${encodedUrl}`} target='_blank' rel='noopener noreferrer'>
|
||||
<div className={styles.postMenuItem}>Google</div>
|
||||
</a>
|
||||
<a href={`https://www.yandex.com/images/search?url=${url}&rpt=imageview`} target='_blank' rel='noreferrer'>
|
||||
<a href={`https://www.yandex.com/images/search?img_url=${encodedUrl}&rpt=imageview`} target='_blank' rel='noopener noreferrer'>
|
||||
<div className={styles.postMenuItem}>Yandex</div>
|
||||
</a>
|
||||
<a href={`https://saucenao.com/search.php?url=${url}`} target='_blank' rel='noreferrer'>
|
||||
<a href={`https://saucenao.com/search.php?url=${encodedUrl}`} target='_blank' rel='noopener noreferrer'>
|
||||
<div className={styles.postMenuItem}>SauceNAO</div>
|
||||
</a>
|
||||
</div>
|
||||
@@ -174,10 +175,8 @@ const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => {
|
||||
|
||||
const location = useLocation();
|
||||
const params = useParams();
|
||||
const isInAllView = isAllView(location.pathname);
|
||||
const isInCatalogView = isCatalogView(location.pathname, params);
|
||||
const isInPostPageView = isPostPageView(location.pathname, params);
|
||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname, useParams());
|
||||
|
||||
const { refs, floatingStyles, context } = useFloating({
|
||||
placement: 'bottom-start',
|
||||
@@ -249,13 +248,21 @@ const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => {
|
||||
role='button'
|
||||
tabIndex={0}
|
||||
onClick={() => {
|
||||
hidden ? unhide() : hide();
|
||||
if (hidden) {
|
||||
unhide();
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
handleClose();
|
||||
}}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' || e.key === ' ') {
|
||||
e.preventDefault();
|
||||
hidden ? unhide() : hide();
|
||||
if (hidden) {
|
||||
unhide();
|
||||
} else {
|
||||
hide();
|
||||
}
|
||||
handleClose();
|
||||
}
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user