diff --git a/src/components/comment-media/comment-media.tsx b/src/components/comment-media/comment-media.tsx index 92db08a4..b715f114 100644 --- a/src/components/comment-media/comment-media.tsx +++ b/src/components/comment-media/comment-media.tsx @@ -11,44 +11,27 @@ import Embed, { canEmbed } from '../embed'; interface MediaProps { commentMediaInfo?: CommentMediaInfo; - post?: Comment; + deleted?: boolean; + displayHeight?: string; + displayWidth?: string; isFloatingEmbed?: boolean; + isOutOfFeed?: boolean; isReply?: boolean; linkHeight?: number; linkWidth?: number; + post?: Comment; + removed?: boolean; + spoiler?: boolean; showThumbnail?: boolean; setShowThumbnail: (showThumbnail: boolean) => void; } -const Thumbnail = ({ commentMediaInfo, isFloatingEmbed, post, setShowThumbnail }: MediaProps) => { - const { deleted, linkHeight, linkWidth, parentCid, removed, spoiler } = post || {}; - const { isDescription, isRules } = post || {}; // custom properties, not from api - const isOutOfFeed = isDescription || isRules || isFloatingEmbed; // virtuoso wrapper unneeded - const isReply = parentCid; - const isDeleted = deleted || removed; - +const Thumbnail = ({ commentMediaInfo, deleted, displayHeight, displayWidth, isFloatingEmbed, isOutOfFeed, isReply, removed, spoiler, setShowThumbnail }: MediaProps) => { + const isMobile = useIsMobile(); const { patternThumbnailUrl, thumbnail, type, url } = commentMediaInfo || {}; const [hasError, setHasError] = useState(false); const handleError = () => setHasError(true); - let displayWidth, displayHeight; - const isMobile = useIsMobile(); - const maxThumbnailSize = isMobile || isReply ? 125 : 250; - - if (linkWidth && linkHeight) { - let scale = Math.min(1, maxThumbnailSize / Math.max(linkWidth, linkHeight)); - displayWidth = `${linkWidth * scale}px`; - displayHeight = `${linkHeight * scale}px`; - } else { - displayWidth = `${maxThumbnailSize}px`; - displayHeight = `${maxThumbnailSize}px`; - } - - if (type === 'audio') { - displayWidth = '100%'; - displayHeight = '100%'; - } - let thumbnailComponent: React.ReactNode = null; const iframeThumbnail = patternThumbnailUrl || thumbnail; const gifFrameUrl = useFetchGifFirstFrame(type === 'gif' ? url : undefined); @@ -78,7 +61,7 @@ const Thumbnail = ({ commentMediaInfo, isFloatingEmbed, post, setShowThumbnail } const linkWithoutThumbnail = url && new URL(url); - return hasError || isDeleted ? ( + return hasError || deleted || removed ? ( File deleted ) : spoiler ? ( setShowThumbnail(false)} /> @@ -149,6 +132,8 @@ const Media = ({ commentMediaInfo, isReply, setShowThumbnail }: MediaProps) => { }; const CommentMedia = ({ commentMediaInfo, isFloatingEmbed, post, showThumbnail, setShowThumbnail }: MediaProps) => { + const { deleted, linkHeight, linkWidth, parentCid, removed, spoiler } = post || {}; + const isReply = parentCid; const { t } = useTranslation(); const isMobile = useIsMobile(); const { url } = commentMediaInfo || {}; @@ -161,13 +146,85 @@ const CommentMedia = ({ commentMediaInfo, isFloatingEmbed, post, showThumbnail, type = 'static gif'; } + let displayWidth, displayHeight; + const maxThumbnailSize = isMobile || isReply ? 125 : 250; + + if (linkWidth && linkHeight) { + let scale = Math.min(1, maxThumbnailSize / Math.max(linkWidth, linkHeight)); + displayWidth = `${linkWidth * scale}px`; + displayHeight = `${linkHeight * scale}px`; + } else { + displayWidth = `${maxThumbnailSize}px`; + displayHeight = `${maxThumbnailSize}px`; + } + + if (type === 'audio') { + displayWidth = '100%'; + displayHeight = '100%'; + } + const { isDescription, isRules } = post || {}; // custom properties, not from api + const isOutOfFeed = isDescription || isRules || isFloatingEmbed; // virtuoso wrapper unneeded + + const [isImageExpanded, setIsImageExpanded] = useState(false); + const { fitExpandedImagesToScreen } = useExpandedMediaStore(); + const mediaDimensions = getMediaDimensions(commentMediaInfo); + const mediaClass = `${isMobile ? styles.mediaMobile : isReply ? styles.mediaDesktopReply : styles.mediaDesktopOp} ${ + fitExpandedImagesToScreen ? styles.fitToScreen : '' + }`; + const thumbnailSmallPadding = isMobile ? styles.thumbnailMobile : styles.thumbnailReplyDesktop; + const thumbnailDimensions = { '--width': displayWidth, '--height': displayHeight } as React.CSSProperties; + return ( - - {url && } - {isMobile && type &&
{`${post?.spoiler ? `${t('spoiler')} - ` : ''} ${getDisplayMediaInfoType(type, t)}`}
} -
- {!showThumbnail && } + {commentMediaInfo?.type === 'image' ? ( + isMobile ? ( + + + setIsImageExpanded(!isImageExpanded)} /> + + {isImageExpanded && type && ( +
+ + {url && url.length > 30 ? url.slice(0, 30) + '...' : url} + {' '} + ({getDisplayMediaInfoType(type, t)} + {mediaDimensions && `, ${mediaDimensions}`}) +
+ )} + {type && !isImageExpanded &&
{`${post?.spoiler ? `${t('spoiler')} - ` : ''} ${getDisplayMediaInfoType(type, t)}`}
} +
+ ) : ( + + setIsImageExpanded(!isImageExpanded)} /> + + ) + ) : ( + <> + + {url && ( + + )} + {isMobile && type &&
{`${post?.spoiler ? `${t('spoiler')} - ` : ''} ${getDisplayMediaInfoType(type, t)}`}
} +
+ {!showThumbnail && } + + )}
); }; diff --git a/src/components/loading-ellipsis/loading-ellipsis.module.css b/src/components/loading-ellipsis/loading-ellipsis.module.css index d840bd8e..7e1e6993 100644 --- a/src/components/loading-ellipsis/loading-ellipsis.module.css +++ b/src/components/loading-ellipsis/loading-ellipsis.module.css @@ -1,22 +1,52 @@ +.nowrap { + white-space: nowrap; +} + +.ellipsis { + display: inline-block; + width: 4ch; +} + .ellipsis:after { - overflow: hidden; display: inline-block; - position: absolute; vertical-align: bottom; - -webkit-animation: ellipsis steps(4,end) 1500ms infinite; - animation: ellipsis steps(4,end) 1500ms infinite; - content: "\2026"; /* ascii code for the ellipsis character */ - width: 0px; + -webkit-animation: ellipsis 1500ms steps(4, end) infinite; + animation: ellipsis 1500ms steps(4, end) infinite; + content: ""; } @keyframes ellipsis { - to { - width: 1.25em; + 0% { + content: ""; + } + 25% { + content: "."; + } + 50% { + content: ".."; + } + 75% { + content: "..."; + } + 100% { + content: ""; } } @-webkit-keyframes ellipsis { - to { - width: 1.25em; + 0% { + content: ""; + } + 25% { + content: "."; + } + 50% { + content: ".."; + } + 75% { + content: "..."; + } + 100% { + content: ""; } } \ No newline at end of file diff --git a/src/components/loading-ellipsis/loading-ellipsis.tsx b/src/components/loading-ellipsis/loading-ellipsis.tsx index 639d62f4..b94f4600 100644 --- a/src/components/loading-ellipsis/loading-ellipsis.tsx +++ b/src/components/loading-ellipsis/loading-ellipsis.tsx @@ -5,7 +5,20 @@ interface LoadingEllipsisProps { } const LoadingEllipsis = ({ string }: LoadingEllipsisProps) => { - return {string}; + const words = string.split(' '); + const lastWord = words.pop(); + const restOfString = words.join(' '); + + return ( + + {restOfString} + {restOfString && ' '} + + {lastWord} + + + + ); }; export default LoadingEllipsis; diff --git a/src/components/post-desktop/post-desktop.tsx b/src/components/post-desktop/post-desktop.tsx index 97aec2d5..5703ed51 100644 --- a/src/components/post-desktop/post-desktop.tsx +++ b/src/components/post-desktop/post-desktop.tsx @@ -165,7 +165,11 @@ const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }: !cid && e.preventDefault()}> c/ - openReplyModal && openReplyModal(cid, postCid, subplebbitAddress)}> + openReplyModal && !(deleted || removed) && openReplyModal(cid, postCid, subplebbitAddress)} + > {shortCid} @@ -205,7 +209,10 @@ const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }: parentCid && replies && replies.map( - (reply: Comment, index: number) => reply?.parentCid === cid && reply?.cid && , + (reply: Comment, index: number) => + reply?.parentCid === cid && + reply?.cid && + !(reply?.deleted || reply?.removed) && , )} @@ -223,6 +230,7 @@ const PostMedia = ({ post }: PostProps) => { const PostMediaContent = ({ post, link, spoiler, t }: { post: any; link: string; spoiler: boolean; t: any }) => { const initialInfo = getCommentMediaInfo(post); const [webpageThumbnail, setWebpageThumbnail] = useState(); + const { isDescription, isRules } = post || {}; // custom properties, not from api useEffect(() => { // some sites have CORS access, so the thumbnail can be fetched client-side, which is helpful if subplebbit.settings.fetchThumbnailUrls is false @@ -285,7 +293,13 @@ const PostMediaContent = ({ post, link, spoiler, t }: { post: any; link: string; {(hasThumbnail || (!hasThumbnail && !showThumbnail) || spoiler) && (
- +
)} diff --git a/src/components/post-mobile/post-mobile.tsx b/src/components/post-mobile/post-mobile.tsx index 1d1dad70..b06ad45b 100644 --- a/src/components/post-mobile/post-mobile.tsx +++ b/src/components/post-mobile/post-mobile.tsx @@ -167,7 +167,11 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P !cid && e.preventDefault()}> c/ - openReplyModal && openReplyModal(cid, postCid, subplebbitAddress)}> + openReplyModal && !(deleted || removed) && openReplyModal(cid, postCid, subplebbitAddress)} + > {shortCid.slice(0, -4)} @@ -191,7 +195,7 @@ const PostMediaContent = ({ post, link, t }: { post: any; link: string; t: any } const initialInfo = getCommentMediaInfo(post); const [webpageThumbnail, setWebpageThumbnail] = useState(); const [showThumbnail, setShowThumbnail] = useState(true); - + const { isDescription, isRules } = post || {}; // custom properties, not from api useEffect(() => { // some sites have CORS access, so the thumbnail can be fetched client-side, which is helpful if subplebbit.settings.fetchThumbnailUrls is false const loadThumbnail = async () => { @@ -206,7 +210,17 @@ const PostMediaContent = ({ post, link, t }: { post: any; link: string; t: any } const commentMediaInfo = webpageThumbnail || initialInfo; const hasThumbnail = getHasThumbnail(commentMediaInfo, link); - return hasThumbnail && ; + return ( + hasThumbnail && ( + + ) + ); }; const ReplyBacklinks = ({ post }: PostProps) => { @@ -219,7 +233,10 @@ const ReplyBacklinks = ({ post }: PostProps) => { replies.length > 0 && (
{replies.map( - (reply: Comment, index: number) => reply?.parentCid === cid && reply?.cid && , + (reply: Comment, index: number) => + reply?.parentCid === cid && + reply?.cid && + !(reply?.deleted || reply?.removed) && , )}
) diff --git a/src/views/home/home.tsx b/src/views/home/home.tsx index 6a64cd3f..4bc14372 100644 --- a/src/views/home/home.tsx +++ b/src/views/home/home.tsx @@ -6,7 +6,6 @@ import styles from './home.module.css'; import packageJson from '../../../package.json'; import useDefaultSubplebbits, { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits'; import useSubplebbitsStats from '../../hooks/use-subplebbits-stats'; -import LoadingEllipsis from '../../components/loading-ellipsis'; import PopularThreadsBox from './popular-threads-box'; import BoardsBox from './boards-box'; @@ -61,7 +60,7 @@ const InfoBox = () => { ); }; -const Stats = ({ multisub, subplebbitAddresses }: { multisub: any; subplebbitAddresses: string[] }) => { +const Stats = ({ subplebbitAddresses }: { subplebbitAddresses: string[] }) => { const { t } = useTranslation(); const stats = useSubplebbitsStats({ subplebbitAddresses }); @@ -84,29 +83,22 @@ const Stats = ({ multisub, subplebbitAddresses }: { multisub: any; subplebbitAdd }, [stats, allStatsLoaded]); const boardsTracked = Object.values(stats).filter((stat: any) => stat && !stat.loading).length; - const enoughStats = boardsTracked >= multisub.length / 2; return (

{t('stats')}

-
- {enoughStats ? ( - <> -
- {t('total_posts')} {totalPosts} -
-
- {t('current_users')} {currentUsers} -
-
- {t('boards_tracked')} {boardsTracked} -
- - ) : ( - - )} +
+
+ {t('total_posts')} {totalPosts} +
+
+ {t('current_users')} {currentUsers} +
+
+ {t('boards_tracked')} {boardsTracked} +
); @@ -224,7 +216,7 @@ const Home = () => { - +
);