mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
perf(feed): optimize posts rendering via props refactoring, memoizations
This commit is contained in:
@@ -115,10 +115,11 @@ const CatalogPost = ({ post }: { post: Comment }) => {
|
||||
subplebbitAddress,
|
||||
timestamp,
|
||||
title,
|
||||
thumbnailUrl,
|
||||
} = post || {};
|
||||
const linkCount = useCountLinksInReplies(post);
|
||||
|
||||
const commentMediaInfo = useCommentMediaInfo(post);
|
||||
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||
|
||||
const { hidden } = useHide({ cid });
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Comment } from '@plebbit/plebbit-react-hooks';
|
||||
import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
|
||||
import { getHostname } from '../../lib/utils/url-utils';
|
||||
import useExpandedMediaStore from '../../stores/use-expanded-media-store';
|
||||
@@ -14,12 +13,14 @@ interface MediaProps {
|
||||
deleted?: boolean;
|
||||
displayHeight?: string;
|
||||
displayWidth?: string;
|
||||
isDescription?: boolean;
|
||||
isRules?: boolean;
|
||||
isFloatingEmbed?: boolean;
|
||||
isOutOfFeed?: boolean;
|
||||
isReply?: boolean;
|
||||
linkHeight?: number;
|
||||
linkWidth?: number;
|
||||
post?: Comment;
|
||||
parentCid?: string;
|
||||
removed?: boolean;
|
||||
spoiler?: boolean;
|
||||
showThumbnail?: boolean;
|
||||
@@ -130,12 +131,12 @@ interface ImageProps {
|
||||
displayHeight: string;
|
||||
displayWidth: string;
|
||||
isOutOfFeed: boolean;
|
||||
post: Comment | undefined;
|
||||
parentCid?: string;
|
||||
spoiler?: boolean;
|
||||
}
|
||||
|
||||
const Image = ({ commentMediaInfo, displayHeight, displayWidth, isOutOfFeed, post }: ImageProps) => {
|
||||
const Image = ({ commentMediaInfo, displayHeight, displayWidth, isOutOfFeed, parentCid, spoiler }: ImageProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { parentCid, spoiler } = post || {};
|
||||
const { type, url } = commentMediaInfo || {};
|
||||
const isReply = parentCid;
|
||||
const isMobile = useIsMobile();
|
||||
@@ -181,7 +182,7 @@ const Image = ({ commentMediaInfo, displayHeight, displayWidth, isOutOfFeed, pos
|
||||
{mediaDimensions && `, ${mediaDimensions}`})
|
||||
</div>
|
||||
)}
|
||||
{type && !isImageExpanded && <div className={styles.fileInfo}>{`${post?.spoiler ? `${t('spoiler')} - ` : ''} ${getDisplayMediaInfoType(type, t)}`}</div>}
|
||||
{type && !isImageExpanded && <div className={styles.fileInfo}>{`${spoiler ? `${t('spoiler')} - ` : ''} ${getDisplayMediaInfoType(type, t)}`}</div>}
|
||||
</span>
|
||||
) : (
|
||||
<span
|
||||
@@ -197,8 +198,20 @@ const Image = ({ commentMediaInfo, displayHeight, displayWidth, isOutOfFeed, pos
|
||||
);
|
||||
};
|
||||
|
||||
const CommentMedia = ({ commentMediaInfo, isFloatingEmbed, post, showThumbnail, setShowThumbnail }: MediaProps) => {
|
||||
const { deleted, linkHeight, linkWidth, parentCid, removed, spoiler } = post || {};
|
||||
const CommentMedia = ({
|
||||
commentMediaInfo,
|
||||
deleted,
|
||||
isDescription,
|
||||
isFloatingEmbed,
|
||||
isRules,
|
||||
linkHeight,
|
||||
linkWidth,
|
||||
parentCid,
|
||||
removed,
|
||||
showThumbnail,
|
||||
setShowThumbnail,
|
||||
spoiler,
|
||||
}: MediaProps) => {
|
||||
const isReply = parentCid;
|
||||
const { t } = useTranslation();
|
||||
const isMobile = useIsMobile();
|
||||
@@ -235,14 +248,20 @@ const CommentMedia = ({ commentMediaInfo, isFloatingEmbed, post, showThumbnail,
|
||||
displayWidth = '100%';
|
||||
displayHeight = '100%';
|
||||
}
|
||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||
const isOutOfFeed = isDescription || isRules || isFloatingEmbed || spoiler; // virtuoso wrapper unneeded
|
||||
const isOutOfFeed = isDescription || isRules || isFloatingEmbed || spoiler || false; // virtuoso wrapper unneeded
|
||||
|
||||
return (
|
||||
<span className={styles.content}>
|
||||
{commentMediaInfo?.type === 'image' ? (
|
||||
// images just enlarge when clicked, so they don't need two separate components
|
||||
<Image commentMediaInfo={commentMediaInfo} displayHeight={displayHeight} displayWidth={displayWidth} isOutOfFeed={isOutOfFeed} post={post} />
|
||||
<Image
|
||||
commentMediaInfo={commentMediaInfo}
|
||||
displayHeight={displayHeight}
|
||||
displayWidth={displayWidth}
|
||||
isOutOfFeed={isOutOfFeed}
|
||||
parentCid={parentCid}
|
||||
spoiler={spoiler}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<span className={`${showThumbnail ? styles.show : styles.hide} ${styles.thumbnail}`}>
|
||||
@@ -259,9 +278,9 @@ const CommentMedia = ({ commentMediaInfo, isFloatingEmbed, post, showThumbnail,
|
||||
setShowThumbnail={setShowThumbnail}
|
||||
/>
|
||||
)}
|
||||
{isMobile && type && <div className={styles.fileInfo}>{`${post?.spoiler ? `${t('spoiler')} - ` : ''} ${getDisplayMediaInfoType(type, t)}`}</div>}
|
||||
{isMobile && type && <div className={styles.fileInfo}>{`${spoiler ? `${t('spoiler')} - ` : ''} ${getDisplayMediaInfoType(type, t)}`}</div>}
|
||||
</span>
|
||||
{!showThumbnail && <Media commentMediaInfo={commentMediaInfo} isReply={post?.parentCid} setShowThumbnail={setShowThumbnail} />}
|
||||
{!showThumbnail && <Media commentMediaInfo={commentMediaInfo} isReply={!!parentCid} setShowThumbnail={setShowThumbnail} />}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
|
||||
@@ -4,7 +4,7 @@ import { Link, useLocation, useParams } from 'react-router-dom';
|
||||
import { Comment, useAuthorAvatar, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
||||
import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js';
|
||||
import styles from '../../views/post/post.module.css';
|
||||
import { getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
|
||||
import { CommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail, getMediaDimensions } from '../../lib/utils/media-utils';
|
||||
import { hashStringToColor, getTextColorForBackground } from '../../lib/utils/post-utils';
|
||||
import { getFormattedDate, getFormattedTimeAgo } from '../../lib/utils/time-utils';
|
||||
import { isValidURL } from '../../lib/utils/url-utils';
|
||||
@@ -222,17 +222,21 @@ const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }:
|
||||
);
|
||||
};
|
||||
|
||||
const PostMedia = ({ post, hasThumbnail }: PostProps) => {
|
||||
interface PostMediaProps {
|
||||
commentMediaInfo: CommentMediaInfo | undefined;
|
||||
hasThumbnail: boolean;
|
||||
isDescription: boolean;
|
||||
isRules: boolean;
|
||||
spoiler: boolean;
|
||||
deleted: boolean;
|
||||
removed: boolean;
|
||||
linkHeight: number;
|
||||
linkWidth: number;
|
||||
parentCid: string;
|
||||
}
|
||||
|
||||
const PostMedia = ({ commentMediaInfo, hasThumbnail, isDescription, isRules, spoiler, deleted, removed, linkHeight, linkWidth, parentCid }: PostMediaProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { spoiler, cid } = post || {};
|
||||
|
||||
// Reset state by remounting component when post changes
|
||||
return <PostMediaContent key={cid} post={post} hasThumbnail={hasThumbnail} spoiler={spoiler} t={t} />;
|
||||
};
|
||||
|
||||
const PostMediaContent = ({ post, hasThumbnail, spoiler, t }: { post: any; hasThumbnail: boolean | undefined; spoiler: boolean; t: any }) => {
|
||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||
const commentMediaInfo = useCommentMediaInfo(post);
|
||||
const { url } = commentMediaInfo || {};
|
||||
let type = commentMediaInfo?.type;
|
||||
const gifFrameUrl = useFetchGifFirstFrame(url);
|
||||
@@ -247,7 +251,7 @@ const PostMediaContent = ({ post, hasThumbnail, spoiler, t }: { post: any; hasTh
|
||||
const [showThumbnail, setShowThumbnail] = useState(true);
|
||||
|
||||
const mediaDimensions = getMediaDimensions(commentMediaInfo);
|
||||
|
||||
console.log('mediaDimensions', mediaDimensions);
|
||||
return (
|
||||
<div className={styles.file}>
|
||||
<div className={styles.fileText}>
|
||||
@@ -268,8 +272,7 @@ const PostMediaContent = ({ post, hasThumbnail, spoiler, t }: { post: any; hasTh
|
||||
)}
|
||||
{showThumbnail && !hasThumbnail && embedUrl && canEmbed(embedUrl) && (
|
||||
<span>
|
||||
{' '}
|
||||
[
|
||||
-[
|
||||
<span className={styles.closeMedia} onClick={() => setShowThumbnail(false)}>
|
||||
{t('open')}
|
||||
</span>
|
||||
@@ -281,10 +284,17 @@ const PostMediaContent = ({ post, hasThumbnail, spoiler, t }: { post: any; hasTh
|
||||
<div className={styles.fileThumbnail}>
|
||||
<CommentMedia
|
||||
commentMediaInfo={commentMediaInfo}
|
||||
post={post}
|
||||
deleted={deleted}
|
||||
isDescription={isDescription}
|
||||
isRules={isRules}
|
||||
removed={removed}
|
||||
linkHeight={linkHeight}
|
||||
linkWidth={linkWidth}
|
||||
showThumbnail={showThumbnail}
|
||||
setShowThumbnail={setShowThumbnail}
|
||||
isOutOfFeed={isDescription || isRules}
|
||||
parentCid={parentCid}
|
||||
spoiler={spoiler}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -300,11 +310,13 @@ const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => {
|
||||
post = editedComment;
|
||||
}
|
||||
|
||||
const { author, cid, deleted, link, postCid, reason, removed, subplebbitAddress } = 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 isRouteLinkToReply = useLocation().pathname.startsWith(`/p/${subplebbitAddress}/c/${cid}`);
|
||||
const { hidden } = useHide({ cid });
|
||||
|
||||
const commentMediaInfo = useCommentMediaInfo(post);
|
||||
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||
|
||||
return (
|
||||
@@ -312,7 +324,20 @@ const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => {
|
||||
<div className={styles.sideArrows}>{'>>'}</div>
|
||||
<div className={`${styles.reply} ${isRouteLinkToReply && styles.highlight}`} data-cid={cid} data-author-address={author?.shortAddress} data-post-cid={postCid}>
|
||||
<PostInfo openReplyModal={openReplyModal} post={post} postReplyCount={postReplyCount} roles={roles} isHidden={hidden} />
|
||||
{link && !hidden && !(deleted || removed) && isValidURL(link) && <PostMedia post={post} hasThumbnail={hasThumbnail} />}
|
||||
{link && !hidden && !(deleted || removed) && isValidURL(link) && (
|
||||
<PostMedia
|
||||
commentMediaInfo={commentMediaInfo}
|
||||
hasThumbnail={hasThumbnail}
|
||||
isDescription={isDescription}
|
||||
isRules={isRules}
|
||||
spoiler={spoiler}
|
||||
deleted={deleted}
|
||||
removed={removed}
|
||||
linkHeight={linkHeight}
|
||||
linkWidth={linkWidth}
|
||||
parentCid={parentCid}
|
||||
/>
|
||||
)}
|
||||
{!hidden && (!(removed || deleted) || ((removed || deleted) && reason)) && <CommentContent comment={post} />}
|
||||
</div>
|
||||
</div>
|
||||
@@ -321,7 +346,7 @@ const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => {
|
||||
|
||||
const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies = true }: PostProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { author, cid, content, deleted, link, pinned, postCid, removed, state, subplebbitAddress } = 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 location = useLocation();
|
||||
@@ -353,7 +378,7 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
|
||||
replyCount: 0,
|
||||
};
|
||||
|
||||
const commentMediaInfo = useCommentMediaInfo(post);
|
||||
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||
|
||||
return (
|
||||
@@ -373,7 +398,20 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
|
||||
)}
|
||||
<div data-cid={cid} data-author-address={author?.shortAddress} data-post-cid={postCid} className={shouldShowSnow() && hasThumbnail ? styles.xmasHatWrapper : ''}>
|
||||
{shouldShowSnow() && hasThumbnail && <img src={`${process.env.PUBLIC_URL}/assets/xmashat.gif`} className={styles.xmasHat} alt='' />}
|
||||
{link && !isHidden && !(deleted || removed) && isValidURL(link) && <PostMedia post={post} hasThumbnail={hasThumbnail} />}
|
||||
{link && !isHidden && !(deleted || removed) && isValidURL(link) && (
|
||||
<PostMedia
|
||||
commentMediaInfo={commentMediaInfo}
|
||||
hasThumbnail={hasThumbnail}
|
||||
isDescription={isDescription}
|
||||
isRules={isRules}
|
||||
spoiler={spoiler}
|
||||
deleted={deleted}
|
||||
removed={removed}
|
||||
linkHeight={linkHeight}
|
||||
linkWidth={linkWidth}
|
||||
parentCid={parentCid}
|
||||
/>
|
||||
)}
|
||||
<PostInfo isHidden={hidden} openReplyModal={openReplyModal} post={post} postReplyCount={replyCount} roles={roles} />
|
||||
{!isHidden && !content && !(deleted || removed) && <div className={styles.spacer} />}
|
||||
{!isHidden && <CommentContent comment={post} />}
|
||||
|
||||
@@ -132,8 +132,8 @@ const BlockBoardButton = ({ address }: { address: string }) => {
|
||||
|
||||
const PostMenuDesktop = ({ post }: { post: Comment }) => {
|
||||
const { t } = useTranslation();
|
||||
const { author, cid, isDescription, isRules, link, postCid, subplebbitAddress } = post || {};
|
||||
const commentMediaInfo = getCommentMediaInfo(post);
|
||||
const { author, cid, isDescription, isRules, link, thumbnailUrl, linkWidth, linkHeight, postCid, subplebbitAddress } = post || {};
|
||||
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||
const { thumbnail, type, url } = commentMediaInfo || {};
|
||||
const [menuBtnRotated, setMenuBtnRotated] = useState(false);
|
||||
|
||||
|
||||
@@ -122,9 +122,9 @@ const BlockBoardButton = ({ address }: { address: string }) => {
|
||||
};
|
||||
|
||||
const PostMenuMobile = ({ post }: { post: Comment }) => {
|
||||
const { author, cid, deleted, isDescription, isRules, link, parentCid, postCid, removed, subplebbitAddress } = post || {};
|
||||
const { author, cid, deleted, isDescription, isRules, link, linkHeight, linkWidth, parentCid, postCid, removed, subplebbitAddress, thumbnailUrl } = post || {};
|
||||
const { isAccountMod, isAccountCommentAuthor } = useEditCommentPrivileges({ commentAuthorAddress: author?.address, subplebbitAddress });
|
||||
const commentMediaInfo = getCommentMediaInfo(post);
|
||||
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||
const { thumbnail, type, url } = commentMediaInfo || {};
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
const { refs, floatingStyles, context } = useFloating({
|
||||
|
||||
@@ -27,7 +27,25 @@ import _ from 'lodash';
|
||||
|
||||
const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: PostProps) => {
|
||||
const { t } = useTranslation();
|
||||
const { author, cid, deleted, link, locked, parentCid, pinned, postCid, reason, removed, shortCid, state, subplebbitAddress, timestamp } = post || {};
|
||||
const {
|
||||
author,
|
||||
cid,
|
||||
deleted,
|
||||
link,
|
||||
linkHeight,
|
||||
linkWidth,
|
||||
locked,
|
||||
parentCid,
|
||||
pinned,
|
||||
postCid,
|
||||
reason,
|
||||
removed,
|
||||
shortCid,
|
||||
state,
|
||||
subplebbitAddress,
|
||||
timestamp,
|
||||
thumbnailUrl,
|
||||
} = post || {};
|
||||
const isReply = parentCid;
|
||||
const title = post?.title?.trim();
|
||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||
@@ -43,7 +61,7 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P
|
||||
const isInPostPageView = isPostPageView(location.pathname, params);
|
||||
const isInSubscriptionsView = isSubscriptionsView(location.pathname, params);
|
||||
|
||||
const commentMediaInfo = useCommentMediaInfo(post);
|
||||
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||
|
||||
const stateString = useStateString(post);
|
||||
@@ -179,25 +197,33 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
{(hasThumbnail || link) && !(deleted || removed) && <PostMediaContent key={cid} post={post} link={link} t={t} />}
|
||||
{(hasThumbnail || link) && !(deleted || removed) && <PostMediaContent key={cid} post={post} link={link} />}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const PostMediaContent = ({ post, link, t }: { post: any; link: string; t: any }) => {
|
||||
const PostMediaContent = ({ post, link }: { post: any; link: string }) => {
|
||||
const [showThumbnail, setShowThumbnail] = useState(true);
|
||||
const { isDescription, isRules } = post || {}; // custom properties, not from api
|
||||
const commentMediaInfo = useCommentMediaInfo(post);
|
||||
const { thumbnailUrl, linkWidth, linkHeight, spoiler, deleted, removed, parentCid } = post || {};
|
||||
const commentMediaInfo = useCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||
|
||||
return (
|
||||
hasThumbnail && (
|
||||
<CommentMedia
|
||||
commentMediaInfo={commentMediaInfo}
|
||||
post={post}
|
||||
deleted={deleted}
|
||||
isDescription={isDescription}
|
||||
isRules={isRules}
|
||||
removed={removed}
|
||||
linkHeight={linkHeight}
|
||||
linkWidth={linkWidth}
|
||||
showThumbnail={showThumbnail}
|
||||
setShowThumbnail={setShowThumbnail}
|
||||
isOutOfFeed={isDescription || isRules}
|
||||
parentCid={parentCid}
|
||||
spoiler={spoiler}
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
@@ -33,8 +33,8 @@ const useCatalogFeedRows = (columnCount: number, feed: any, isFeedLoaded: boolea
|
||||
|
||||
// show account comments instantly in the feed once published (cid defined), instead of waiting for the feed to update
|
||||
const filteredComments = accountComments.filter((comment) => {
|
||||
const { cid, deleted, link, postCid, removed, state, subplebbitAddress, timestamp } = comment || {};
|
||||
const commentMediaInfo = getCommentMediaInfo(comment);
|
||||
const { cid, deleted, link, postCid, removed, state, subplebbitAddress, timestamp, thumbnailUrl, linkWidth, linkHeight } = comment || {};
|
||||
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||
const isMediaShowed = getHasThumbnail(commentMediaInfo, link);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useLocation, useParams } from 'react-router-dom';
|
||||
import { Comment } from '@plebbit/plebbit-react-hooks';
|
||||
import { getCommentMediaInfo, fetchWebpageThumbnailIfNeeded } from '../lib/utils/media-utils';
|
||||
import { isPendingPostView, isPostPageView } from '../lib/utils/view-utils';
|
||||
|
||||
export const useCommentMediaInfo = (comment: Comment) => {
|
||||
export const useCommentMediaInfo = (link: string, thumbnailUrl: string, linkWidth: number, linkHeight: number) => {
|
||||
const location = useLocation();
|
||||
const params = useParams();
|
||||
const isInPostPageView = isPostPageView(location.pathname, params);
|
||||
@@ -12,7 +11,7 @@ export const useCommentMediaInfo = (comment: Comment) => {
|
||||
|
||||
// some sites have CORS access, so the thumbnail can be fetched client-side, which is helpful if subplebbit.settings.fetchThumbnailUrls is false
|
||||
const fetchThumbnail = useCallback(async () => {
|
||||
let commentMediaInfo = getCommentMediaInfo(comment);
|
||||
let commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||
if (commentMediaInfo?.type === 'webpage' && !commentMediaInfo.thumbnail) {
|
||||
const newMediaInfo = await fetchWebpageThumbnailIfNeeded(commentMediaInfo);
|
||||
// Fetch the dimensions of the thumbnail
|
||||
@@ -30,7 +29,7 @@ export const useCommentMediaInfo = (comment: Comment) => {
|
||||
commentMediaInfo = newMediaInfo;
|
||||
}
|
||||
return commentMediaInfo;
|
||||
}, [comment]);
|
||||
}, [link, thumbnailUrl, linkWidth, linkHeight]);
|
||||
|
||||
useEffect(() => {
|
||||
// don't fetch in feed view, it displaces the posts
|
||||
@@ -39,5 +38,5 @@ export const useCommentMediaInfo = (comment: Comment) => {
|
||||
}
|
||||
}, [fetchThumbnail, isInPostPageView, isInPendingPostView]);
|
||||
|
||||
return getCommentMediaInfo(comment);
|
||||
return getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||
};
|
||||
|
||||
@@ -22,10 +22,10 @@ const usePopularPosts = (subplebbits: Subplebbit[]) => {
|
||||
|
||||
if (subplebbit?.posts?.pages?.hot?.comments) {
|
||||
for (const post of Object.values(subplebbit.posts.pages.hot.comments as Comment)) {
|
||||
const { deleted, link, locked, pinned, removed, replyCount, timestamp } = post;
|
||||
const { deleted, link, linkHeight, linkWidth, locked, pinned, removed, replyCount, thumbnailUrl, timestamp } = post;
|
||||
|
||||
try {
|
||||
const commentMediaInfo = getCommentMediaInfo(post);
|
||||
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||
const hasThumbnail = getHasThumbnail(commentMediaInfo, link);
|
||||
|
||||
if (
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import localForageLru from '@plebbit/plebbit-react-hooks/dist/lib/localforage-lru/index.js';
|
||||
import { Comment } from '@plebbit/plebbit-react-hooks';
|
||||
import extName from 'ext-name';
|
||||
import { canEmbed } from '../../components/embed';
|
||||
import memoize from 'memoizee';
|
||||
@@ -13,7 +12,8 @@ export interface CommentMediaInfo {
|
||||
thumbnailWidth?: number;
|
||||
thumbnailHeight?: number;
|
||||
patternThumbnailUrl?: string;
|
||||
post?: Comment;
|
||||
linkWidth?: number;
|
||||
linkHeight?: number;
|
||||
}
|
||||
|
||||
export const getDisplayMediaInfoType = (type: string, t: any) => {
|
||||
@@ -35,19 +35,20 @@ export const getDisplayMediaInfoType = (type: string, t: any) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const getHasThumbnail = (commentMediaInfo: CommentMediaInfo | undefined, link: string | undefined): boolean => {
|
||||
const iframeThumbnail = commentMediaInfo?.patternThumbnailUrl || commentMediaInfo?.thumbnail;
|
||||
return link &&
|
||||
commentMediaInfo &&
|
||||
(commentMediaInfo.type === 'image' ||
|
||||
commentMediaInfo.type === 'video' ||
|
||||
commentMediaInfo.type === 'audio' ||
|
||||
commentMediaInfo.type === 'gif' ||
|
||||
(commentMediaInfo.type === 'webpage' && commentMediaInfo.thumbnail) ||
|
||||
(commentMediaInfo.type === 'iframe' && iframeThumbnail))
|
||||
? true
|
||||
: false;
|
||||
};
|
||||
export const getHasThumbnail = memoize(
|
||||
(commentMediaInfo: CommentMediaInfo | undefined, link: string | undefined): boolean => {
|
||||
if (!link || !commentMediaInfo) return false;
|
||||
|
||||
const { type, thumbnail, patternThumbnailUrl } = commentMediaInfo;
|
||||
|
||||
if (type === 'image' || type === 'video' || type === 'audio' || type === 'gif') return true;
|
||||
if (type === 'webpage' && thumbnail) return true;
|
||||
if (type === 'iframe' && (patternThumbnailUrl || thumbnail)) return true;
|
||||
|
||||
return false;
|
||||
},
|
||||
{ max: 1000 },
|
||||
);
|
||||
|
||||
const getYouTubeVideoId = (url: URL): string | null => {
|
||||
if (url.host.includes('youtu.be')) {
|
||||
@@ -182,55 +183,61 @@ const fetchWebpageThumbnail = async (url: string): Promise<string | undefined> =
|
||||
}
|
||||
};
|
||||
|
||||
export const getCommentMediaInfo = (comment: Comment): CommentMediaInfo | undefined => {
|
||||
if (!comment?.thumbnailUrl && !comment?.link) {
|
||||
export const getCommentMediaInfo = (link: string, thumbnailUrl: string, linkWidth: number, linkHeight: number): CommentMediaInfo | undefined => {
|
||||
if (!thumbnailUrl && !link) {
|
||||
return;
|
||||
}
|
||||
const linkInfo = comment.link ? getLinkMediaInfo(comment.link) : undefined;
|
||||
const linkInfo = link ? getLinkMediaInfo(link) : undefined;
|
||||
if (linkInfo) {
|
||||
return {
|
||||
...linkInfo,
|
||||
thumbnail: comment.thumbnailUrl || linkInfo.thumbnail,
|
||||
post: comment,
|
||||
thumbnail: thumbnailUrl || linkInfo.thumbnail,
|
||||
linkWidth,
|
||||
linkHeight,
|
||||
};
|
||||
}
|
||||
return;
|
||||
};
|
||||
|
||||
export const getMediaDimensions = (commentMediaInfo: CommentMediaInfo | undefined): string => {
|
||||
if (!commentMediaInfo) return '';
|
||||
const EMBED_DIMENSIONS = {
|
||||
'youtube.com': '800x450',
|
||||
'youtu.be': '800x450',
|
||||
'instagram.com': '360x420',
|
||||
'reddit.com': '500x520',
|
||||
'tiktok.com': '400x780',
|
||||
'x.com': '550x580',
|
||||
'twitter.com': '550x580',
|
||||
'soundcloud.com': '700x166',
|
||||
} as const;
|
||||
|
||||
const { type, url, post } = commentMediaInfo;
|
||||
export const getMediaDimensions = memoize(
|
||||
(commentMediaInfo: CommentMediaInfo | undefined): string => {
|
||||
if (!commentMediaInfo) return '';
|
||||
|
||||
if (type === 'iframe' && url) {
|
||||
const embedUrl = new URL(url);
|
||||
if (canEmbed(embedUrl)) {
|
||||
// hardcoded dimensions from embed.module.css
|
||||
if (embedUrl.hostname.includes('youtube.com') || embedUrl.hostname.includes('youtu.be')) {
|
||||
return '800x450';
|
||||
} else if (embedUrl.hostname.includes('instagram.com')) {
|
||||
return '360x420';
|
||||
} else if (embedUrl.hostname.includes('reddit.com')) {
|
||||
return '500x520';
|
||||
} else if (embedUrl.hostname.includes('tiktok.com')) {
|
||||
return '400x780';
|
||||
} else if (embedUrl.hostname.includes('x.com') || embedUrl.hostname.includes('twitter.com')) {
|
||||
return '550x580';
|
||||
} else if (embedUrl.hostname.includes('soundcloud.com')) {
|
||||
return '700x166';
|
||||
const { type, url, linkWidth, linkHeight } = commentMediaInfo;
|
||||
|
||||
if (type === 'iframe' && url) {
|
||||
const embedUrl = new URL(url);
|
||||
if (canEmbed(embedUrl)) {
|
||||
const hostname = embedUrl.hostname;
|
||||
for (const [site, dimensions] of Object.entries(EMBED_DIMENSIONS)) {
|
||||
if (hostname.includes(site)) {
|
||||
return dimensions;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (type === 'audio') {
|
||||
return '700x240';
|
||||
} else if (type === 'image' || type === 'video' || type === 'gif') {
|
||||
if (linkWidth && linkHeight) {
|
||||
return `${linkWidth}x${linkHeight}`;
|
||||
}
|
||||
}
|
||||
} else if (type === 'audio') {
|
||||
return '700x240'; // hardcoded dimensions from embed.module.css
|
||||
} else if (type === 'image' || type === 'video' || type === 'gif') {
|
||||
// media dimensions calculated by API
|
||||
if (post?.linkWidth && post?.linkHeight) {
|
||||
return `${post.linkWidth}x${post.linkHeight}`;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
};
|
||||
return '';
|
||||
},
|
||||
{ max: 1000 },
|
||||
);
|
||||
|
||||
const thumbnailUrlsDb = localForageLru.createInstance({ name: 'plebchanThumbnailUrls', size: 500 });
|
||||
|
||||
|
||||
@@ -53,7 +53,9 @@ const useCatalogFiltersStore = create(
|
||||
// filterItems
|
||||
} = state;
|
||||
|
||||
const hasThumbnail = getHasThumbnail(getCommentMediaInfo(comment), comment?.link);
|
||||
const { link, linkHeight, linkWidth, thumbnailUrl } = comment || {};
|
||||
|
||||
const hasThumbnail = getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), link);
|
||||
// const title = comment?.title?.toLowerCase() || '';
|
||||
// const content = comment?.content?.toLowerCase() || '';
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ import SubplebbitRules from '../../components/subplebbit-rules';
|
||||
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
|
||||
|
||||
const threadsWithoutImagesFilter = (comment: Comment) => {
|
||||
if (!getHasThumbnail(getCommentMediaInfo(comment), comment?.link)) {
|
||||
const { link, linkHeight, linkWidth, thumbnailUrl } = comment || {};
|
||||
if (!getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), link)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -78,14 +79,14 @@ const Board = () => {
|
||||
const filteredComments = useMemo(
|
||||
() =>
|
||||
accountComments.filter((comment) => {
|
||||
const { cid, deleted, postCid, removed, state, timestamp } = comment || {};
|
||||
const { cid, deleted, link, linkHeight, linkWidth, postCid, removed, state, thumbnailUrl, timestamp } = comment || {};
|
||||
return (
|
||||
!deleted &&
|
||||
!removed &&
|
||||
timestamp > Date.now() / 1000 - 60 * 60 &&
|
||||
state === 'succeeded' &&
|
||||
cid &&
|
||||
(hideThreadsWithoutImages ? getHasThumbnail(getCommentMediaInfo(comment), comment?.link) : true) &&
|
||||
(hideThreadsWithoutImages ? getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), comment?.link) : true) &&
|
||||
cid === postCid &&
|
||||
comment?.subplebbitAddress === subplebbitAddress &&
|
||||
!feed.some((post) => post.cid === cid)
|
||||
|
||||
@@ -22,7 +22,8 @@ import styles from './catalog.module.css';
|
||||
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
|
||||
|
||||
const threadsWithoutImagesFilter = (comment: Comment) => {
|
||||
if (!getHasThumbnail(getCommentMediaInfo(comment), comment?.link)) {
|
||||
const { link, linkHeight, linkWidth, thumbnailUrl } = comment || {};
|
||||
if (!getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), link)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
@@ -98,14 +99,14 @@ const Catalog = () => {
|
||||
const filteredComments = useMemo(
|
||||
() =>
|
||||
accountComments.filter((comment) => {
|
||||
const { cid, deleted, postCid, removed, state, timestamp } = comment || {};
|
||||
const { cid, deleted, link, linkHeight, linkWidth, postCid, removed, state, thumbnailUrl, timestamp } = comment || {};
|
||||
return (
|
||||
!deleted &&
|
||||
!removed &&
|
||||
timestamp > Date.now() / 1000 - 60 * 60 &&
|
||||
state === 'succeeded' &&
|
||||
cid &&
|
||||
(hideThreadsWithoutImages ? getHasThumbnail(getCommentMediaInfo(comment), comment?.link) : true) &&
|
||||
(hideThreadsWithoutImages ? getHasThumbnail(getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight), comment?.link) : true) &&
|
||||
cid === postCid &&
|
||||
comment?.subplebbitAddress === subplebbitAddress &&
|
||||
!feed.some((post) => post.cid === cid)
|
||||
|
||||
@@ -26,8 +26,8 @@ export const ContentPreview = ({ content, maxLength = 99 }: { content: string; m
|
||||
};
|
||||
|
||||
const PopularThreadCard = ({ post, boardTitle, boardShortAddress }: PopularThreadProps) => {
|
||||
const { cid, content, subplebbitAddress, title } = post || {};
|
||||
const commentMediaInfo = getCommentMediaInfo(post);
|
||||
const { cid, content, link, linkHeight, linkWidth, subplebbitAddress, thumbnailUrl, title } = post || {};
|
||||
const commentMediaInfo = getCommentMediaInfo(link, thumbnailUrl, linkWidth, linkHeight);
|
||||
|
||||
return (
|
||||
<div className={styles.popularThread} key={cid}>
|
||||
|
||||
Reference in New Issue
Block a user