Files
5chan/src/components/comment-content/comment-content.tsx
T

146 lines
6.3 KiB
TypeScript
Raw Normal View History

2025-03-04 16:45:34 +01:00
import { Fragment, useState } from 'react';
2025-02-22 22:22:42 +01:00
import { useLocation, useParams } from 'react-router-dom';
import { Trans, useTranslation } from 'react-i18next';
import { Comment, useComment } from '@plebbit/plebbit-react-hooks';
import useSubplebbitsPagesStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits-pages';
import Plebbit from '@plebbit/plebbit-js';
2025-02-22 22:22:42 +01:00
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
import { isPostPageView } from '../../lib/utils/view-utils';
import useIsMobile from '../../hooks/use-is-mobile';
import useStateString from '../../hooks/use-state-string';
import LoadingEllipsis from '../../components/loading-ellipsis';
import ReplyQuotePreview from '../../components/reply-quote-preview';
import Markdown from '../../components/markdown';
import Tooltip from '../../components/tooltip';
import styles from '../../views/post/post.module.css';
import _ from 'lodash';
const CommentContent = ({ comment: post }: { comment: Comment }) => {
2025-02-22 22:22:42 +01:00
const { t } = useTranslation();
const params = useParams();
const location = useLocation();
const isInPostView = isPostPageView(location.pathname, params);
const [showOriginal, setShowOriginal] = useState(false);
const isMobile = useIsMobile();
const { cid, content, deleted, edit, original, parentCid, postCid, pendingApproval, reason, removed, state, subplebbitAddress } = post || {};
const banned = !!post?.author?.subplebbit?.banExpiresAt;
2025-02-22 22:22:42 +01:00
const [showFullComment, setShowFullComment] = useState(false);
const displayContent =
content &&
(!isInPostView && content.length > 1000 && !showFullComment
? content.slice(0, 1000)
: isInPostView && content.length > 2000 && !showFullComment
2026-01-08 17:09:35 +01:00
? content.slice(0, 2000)
: content);
2025-02-22 22:22:42 +01:00
const quotelinkReplyFromStore = useSubplebbitsPagesStore((state) => state.comments[parentCid]);
const quotelinkReplyFromHook = useComment({ commentCid: parentCid, onlyIfCached: true });
// Prefer hook version to ensure 'number' property is populated for deeper nested replies in Virtuoso
const quotelinkReply = quotelinkReplyFromHook?.number !== undefined ? quotelinkReplyFromHook : quotelinkReplyFromStore;
const isReply = !!parentCid;
const isReplyingToReply = isReply && parentCid !== postCid;
2025-02-22 22:22:42 +01:00
const stateString = useStateString(post);
const loadingString = <div className={styles.stateString}>{stateString !== 'Failed' ? <LoadingEllipsis string={stateString || t('loading')} /> : stateString}</div>;
return (
<blockquote className={`${styles.postMessage} ${!isReply && isMobile && styles.clampLines}`}>
{isReply && state !== 'failed' && isReplyingToReply && !(deleted || removed) && <ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={quotelinkReply} />}
2025-02-22 22:22:42 +01:00
{removed ? (
reason ? (
<>
<span className={styles.redEditMessage}>({t('this_post_was_removed')})</span>
<br />
<br />
<span className={styles.grayEditMessage}>{`${_.capitalize(t('reason'))}: "${reason}"`}</span>
</>
) : (
<span className={styles.grayEditMessage}>{_.capitalize(t('this_post_was_removed'))}.</span>
)
) : deleted ? (
reason ? (
<>
<span className={styles.grayEditMessage}>{t('user_deleted_this_post')}</span>{' '}
<span className={styles.grayEditMessage}>{`${_.capitalize(t('reason'))}: "${reason}"`}</span>
</>
) : (
<span className={styles.grayEditMessage}>{t('user_deleted_this_post')}</span>
)
) : (
<>
{!showOriginal && <Markdown content={displayContent} />}
2026-01-08 17:09:35 +01:00
{pendingApproval && (
<>
<br />
<span className={styles.pendingApproval}>({t('pending_mod_approval')})</span>
2026-01-08 17:09:35 +01:00
</>
)}
2025-02-22 22:22:42 +01:00
{((!isInPostView && content?.length > 1000 && !showFullComment) || (isInPostView && content?.length > 2000 && !showFullComment)) && (
<span className={styles.abbr}>
<br />
<br />
2025-03-05 18:30:41 +01:00
<Trans i18nKey={'comment_too_long'} shouldUnescape={true} components={{ 1: <span key={cid} onClick={() => setShowFullComment(true)} /> }} />
2025-02-22 22:22:42 +01:00
</span>
)}
{edit && original?.content !== content && (
<span className={styles.editedInfo}>
{showOriginal && <Markdown content={original?.content} />}
<br />
<br />
<Trans
i18nKey={'comment_edited_at_timestamp'}
values={{ timestamp: getFormattedDate(edit?.timestamp) }}
shouldUnescape={true}
2025-03-05 18:30:41 +01:00
components={{
1: <Tooltip key={edit?.timestamp} content={getFormattedTimeAgo(edit?.timestamp)} children={<Fragment key={edit?.timestamp}></Fragment>} />,
}}
2025-02-22 22:22:42 +01:00
/>{' '}
{reason && <>{t('reason_reason', { reason: reason, interpolation: { escapeValue: false } })} </>}
{showOriginal ? (
<Trans
i18nKey={'click_here_to_hide_original'}
shouldUnescape={true}
2025-03-05 18:30:41 +01:00
components={{ 1: <span key={cid} className={styles.showOriginal} onClick={() => setShowOriginal(!showOriginal)} /> }}
2025-02-22 22:22:42 +01:00
/>
) : (
<Trans
i18nKey={'click_here_to_show_original'}
shouldUnescape={true}
2025-03-05 18:30:41 +01:00
components={{ 1: <span key={cid} className={styles.showOriginal} onClick={() => setShowOriginal(!showOriginal)} /> }}
2025-02-22 22:22:42 +01:00
/>
)}
</span>
)}
</>
)}
{banned && (
2025-02-22 22:22:42 +01:00
<span className={styles.removedContent}>
<br />
<br />
<Tooltip
children={`(${t('user_banned')})`}
content={`${t('ban_expires_at', {
address: subplebbitAddress && Plebbit.getShortAddress({ address: subplebbitAddress }),
timestamp: getFormattedDate(post?.author?.subplebbit?.banExpiresAt),
2025-02-22 22:22:42 +01:00
interpolation: { escapeValue: false },
})}${reason ? `. ${_.capitalize(t('reason'))}: "${reason}"` : ''}`}
/>
</span>
)}
2025-02-22 22:22:42 +01:00
{!cid && state === 'pending' && stateString !== 'Failed' && (
<>
<br />
<br />
{loadingString}
</>
)}
</blockquote>
);
};
export default CommentContent;