From 2c1f74f528b916844c74887a9ca5c9786a3e565b Mon Sep 17 00:00:00 2001 From: "plebeius.eth" Date: Thu, 16 May 2024 23:05:00 +0200 Subject: [PATCH] refactor(post): use separate files for post desktop and post mobile --- src/components/post/post-desktop/index.ts | 1 + .../post/post-desktop/post-desktop.tsx | 280 +++++++++++ src/components/post/post-mobile/index.ts | 1 + .../post/post-mobile/post-mobile.tsx | 198 ++++++++ src/components/post/post.tsx | 462 +----------------- 5 files changed, 485 insertions(+), 457 deletions(-) create mode 100644 src/components/post/post-desktop/index.ts create mode 100644 src/components/post/post-desktop/post-desktop.tsx create mode 100644 src/components/post/post-mobile/index.ts create mode 100644 src/components/post/post-mobile/post-mobile.tsx diff --git a/src/components/post/post-desktop/index.ts b/src/components/post/post-desktop/index.ts new file mode 100644 index 00000000..95dd47f0 --- /dev/null +++ b/src/components/post/post-desktop/index.ts @@ -0,0 +1 @@ +export { default } from './post-desktop'; diff --git a/src/components/post/post-desktop/post-desktop.tsx b/src/components/post/post-desktop/post-desktop.tsx new file mode 100644 index 00000000..ec479977 --- /dev/null +++ b/src/components/post/post-desktop/post-desktop.tsx @@ -0,0 +1,280 @@ +import { useEffect, useRef, useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; +import { useAccount } from '@plebbit/plebbit-react-hooks'; +import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js'; +import { getCommentMediaInfo, getDisplayMediaInfoType, getHasThumbnail } from '../../../lib/utils/media-utils'; +import { getFormattedDate } from '../../../lib/utils/time-utils'; +import useCountLinksInReplies from '../../../hooks/use-count-links-in-replies'; +import useReplies from '../../../hooks/use-replies'; +import useStateString from '../../../hooks/use-state-string'; +import CommentMedia from '../../comment-media'; +import { canEmbed } from '../../embed'; +import LoadingEllipsis from '../../loading-ellipsis'; +import Markdown from '../../markdown'; +import { PostProps } from '../post'; +import styles from '../post.module.css'; +import _ from 'lodash'; + +const PostInfoDesktop = ({ isInPostPage, openReplyModal, post, roles }: PostProps) => { + const { t } = useTranslation(); + const { author, cid, locked, pinned, parentCid, postCid, shortCid, state, subplebbitAddress, timestamp, title } = post || {}; + const { address, displayName, shortAddress } = author || {}; + const { isDescription, isRules } = post || {}; // custom properties, not from api + + const stateString = useStateString(post); + + const isReply = parentCid; + + const shortDisplayName = displayName?.trim().length > 20 ? displayName?.trim().slice(0, 20).trim() + '...' : displayName?.trim(); + const authorRole = roles?.[address]?.role; + const displayTitle = title && title.length > 75 ? title?.slice(0, 75) + '...' : title; + + // pending reply by account is not yet published + const account = useAccount(); + const accountShortAddress = account?.author?.shortAddress; + + const [menuBtnRotated, setMenuBtnRotated] = useState(false); + const menuRef = useRef(null); + + const handleMenuButtonClick = () => { + setMenuBtnRotated((prev) => !prev); + }; + + const handleClickOutside = (event: MouseEvent) => { + if (menuRef.current && !menuRef.current.contains(event.target as Node)) { + setMenuBtnRotated(false); + } + }; + + useEffect(() => { + if (menuBtnRotated) { + document.addEventListener('mousedown', handleClickOutside); + } else { + document.removeEventListener('mousedown', handleClickOutside); + } + return () => { + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [menuBtnRotated]); + + return ( +
+ + + + {title && {displayTitle} } + + + {shortDisplayName || _.capitalize(t('anonymous'))} + {authorRole && ` ## Board ${authorRole}`}{' '} + + {!(isDescription || isRules) && (u/{shortAddress || accountShortAddress}) } + + + {getFormattedDate(timestamp)} + {isDescription || isRules ? '' : ' '} + + + {!(isDescription || isRules) && ( + + !cid && e.preventDefault()}> + c/ + + {!cid ? ( + {state === 'failed' || stateString === 'Failed' ? 'Failed' : 'Pending'} + ) : ( + openReplyModal && openReplyModal(cid)}> + {shortCid} + + )} + + )} + {pinned && ( + + + + )} + {locked && ( + + + + )} + {!isInPostPage && !isReply && ( + + [{_.capitalize(t('reply'))}] + + )} + + + + ▶ + + +
+ ); +}; + +const PostMediaDesktop = ({ post }: PostProps) => { + const { t } = useTranslation(); + const { link, linkHeight, linkWidth, parentCid } = post || {}; + const { isDescription, isRules } = post || {}; // custom properties, not from api + const commentMediaInfo = getCommentMediaInfo(post); + const { type, url } = commentMediaInfo || {}; + const embedUrl = url && new URL(url); + const hasThumbnail = getHasThumbnail(commentMediaInfo, link); + const [showThumbnail, setShowThumbnail] = useState(true); + + const isReply = parentCid; + + return ( + url && ( +
+
+ {t('link')}:{' '} + + {url.length > 30 ? url.slice(0, 30) + '...' : url} + {' '} + ({type && _.lowerCase(getDisplayMediaInfoType(type, t))}) + {!showThumbnail && (type === 'iframe' || type === 'video' || type === 'audio') && ( + + {' '} + [ + setShowThumbnail(true)}> + {t('close')} + + ] + + )} + {showThumbnail && !hasThumbnail && embedUrl && canEmbed(embedUrl) && ( + + {' '} + [ + setShowThumbnail(false)}> + {t('open')} + + ] + + )} +
+ {(hasThumbnail || (!hasThumbnail && !showThumbnail)) && ( + + )} +
+ ) + ); +}; + +const PostMessageDesktop = ({ isInPostPage, post }: PostProps) => { + const { cid, content, parentCid, postCid, state, subplebbitAddress } = post || {}; + const displayContent = content && !isInPostPage && content.length > 1000 ? content?.slice(0, 1000) + '(...)' : content; + + const isReply = parentCid; + const isReplyingToReply = postCid !== parentCid; + + const stateString = useStateString(post); + + const loadingString = stateString && ( +
{stateString !== 'Failed' ? : stateString}
+ ); + + return ( + content && ( +
+ {isReply && isReplyingToReply && ( + <> + + {`c/${parentCid && Plebbit.getShortCid(parentCid)}`} + +
+ + )} + + {!isReply && content.length > 1000 && !isInPostPage && ( + +
+ }} /> +
+ )} + {!cid && state === 'pending' && stateString !== 'Failed' && ( + <> +
+ {loadingString} + + )} +
+ ) + ); +}; + +const PostDesktop = ({ isInPostPage, isPendingPostPage, openReplyModal, post, roles, showAllReplies }: PostProps) => { + const { cid, content, pinned, replyCount, subplebbitAddress } = post || {}; + const { isDescription, isRules } = post || {}; // custom properties, not from api + + const replies = useReplies(post); + const visiblelinksCount = useCountLinksInReplies(post, 5); + const totallinksCount = useCountLinksInReplies(post); + const repliesCount = pinned ? replyCount : replyCount - 5; + const linksCount = pinned ? totallinksCount : totallinksCount - visiblelinksCount; + + return ( +
+
+
+
+ {!isInPostPage && ( + + + + )} + + + {!content &&
} + + {!isDescription && !isRules && !isPendingPostPage && (replies.length > 5 || (pinned && replies.length > 0)) && !isInPostPage && ( + + + + + {linksCount > 0 ? ( + }} + values={{ repliesCount, linksCount }} + /> + ) : ( + }} values={{ repliesCount }} /> + )} + + )} + {!(pinned && !isInPostPage) && + !isPendingPostPage && + !isDescription && + !isRules && + replies && + (showAllReplies ? replies : replies.slice(-5)).map((reply, index) => ( +
+
+
{'>>'}
+
+ + + +
+
+
+ ))} +
+ ); +}; + +export default PostDesktop; diff --git a/src/components/post/post-mobile/index.ts b/src/components/post/post-mobile/index.ts new file mode 100644 index 00000000..dffdbac4 --- /dev/null +++ b/src/components/post/post-mobile/index.ts @@ -0,0 +1 @@ +export { default } from './post-mobile'; diff --git a/src/components/post/post-mobile/post-mobile.tsx b/src/components/post/post-mobile/post-mobile.tsx new file mode 100644 index 00000000..bc482ff2 --- /dev/null +++ b/src/components/post/post-mobile/post-mobile.tsx @@ -0,0 +1,198 @@ +import { useState } from 'react'; +import { Trans, useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; +import { useAccount } from '@plebbit/plebbit-react-hooks'; +import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js'; +import { getCommentMediaInfo, getHasThumbnail } from '../../../lib/utils/media-utils'; +import { getFormattedDate } from '../../../lib/utils/time-utils'; +import useCountLinksInReplies from '../../../hooks/use-count-links-in-replies'; +import useReplies from '../../../hooks/use-replies'; +import useStateString from '../../../hooks/use-state-string'; +import CommentMedia from '../../comment-media'; +import LoadingEllipsis from '../../loading-ellipsis'; +import Markdown from '../../markdown'; +import { PostProps } from '../post'; +import styles from '../post.module.css'; +import _ from 'lodash'; + +const PostInfoAndMediaMobile = ({ openReplyModal, post, roles }: PostProps) => { + const { t } = useTranslation(); + const { author, cid, link, linkHeight, linkWidth, locked, parentCid, pinned, shortCid, state, subplebbitAddress, timestamp, title } = post || {}; + const { isDescription, isRules } = post || {}; // custom properties, not from api + const { address, displayName, shortAddress } = author || {}; + + const authorRole = roles?.[address]?.role; + const shortDisplayName = displayName?.trim().length > 20 ? displayName?.trim().slice(0, 20).trim() + '...' : displayName?.trim(); + const displayTitle = title && title.length > 30 ? title?.slice(0, 30) + '(...)' : title; + + const commentMediaInfo = getCommentMediaInfo(post); + const hasThumbnail = getHasThumbnail(commentMediaInfo, link); + const [showThumbnail, setShowThumbnail] = useState(true); + + const isReply = parentCid; + + // pending reply by account is not yet published + const account = useAccount(); + const accountShortAddress = account?.author?.shortAddress; + + const stateString = useStateString(post); + + return ( + <> +
+ + ... + + + + {shortDisplayName || _.capitalize(t('anonymous'))} + {authorRole && ` ## Board ${authorRole}`}{' '} + + {!(isDescription || isRules) && (u/{shortAddress || accountShortAddress})} + {pinned && ( + + + + )} + {locked && ( + + + + )} + {title && ( + <> +
+ {displayTitle} + + )} +
+ + {getFormattedDate(timestamp)}{' '} + {!(isDescription || isRules) && ( + + !cid && e.preventDefault()}> + c/ + + {!cid ? ( + {state === 'failed' || stateString === 'Failed' ? 'Failed' : 'Pending'} + ) : ( + openReplyModal && openReplyModal(cid)}> + {shortCid} + + )} + + )} + +
+ {(hasThumbnail || link) && ( + + )} + + ); +}; + +const PostMessageMobile = ({ isInPostPage, post }: PostProps) => { + const { cid, content, parentCid, postCid, state, subplebbitAddress } = post || {}; + const displayContent = content && !isInPostPage && content.length > 1000 ? content?.slice(0, 1000) : content; + + const isReply = parentCid; + const isReplyingToReply = postCid !== parentCid; + + const stateString = useStateString(post); + + const loadingString = stateString && ( +
{stateString !== 'Failed' ? : stateString}
+ ); + + return ( + content && ( +
+ {isReply && isReplyingToReply && ( + <> + + {`c/${parentCid && Plebbit.getShortCid(parentCid)}`} + +
+ + )} + + {!isReply && content.length > 1000 && !isInPostPage && ( + +
+ }} /> +
+ )} + {!cid && state === 'pending' && stateString !== 'Failed' && ( + <> +
+ {loadingString} + + )} +
+ ) + ); +}; + +const PostMobile = ({ isInPostPage, isPendingPostPage, openReplyModal, post, roles, showAllReplies }: PostProps) => { + const { t } = useTranslation(); + const { cid, pinned, replyCount, subplebbitAddress } = post || {}; + const { isDescription, isRules } = post || {}; // custom properties, not from api + + const linksCount = useCountLinksInReplies(post); + + const replies = useReplies(post); + + return ( +
+
+
+
+
+
+
+ + +
+ {!isInPostPage && !isPendingPostPage && ( +
+ + {replyCount > 0 && `${replyCount} Replies`} + {linksCount > 0 && ` / ${linksCount} Links`} + + + {t('view_thread')} + +
+ )} +
+ {!(pinned && !isInPostPage) && + !isPendingPostPage && + !isDescription && + !isRules && + replies && + (showAllReplies ? replies : replies.slice(-5)).map((reply) => ( +
+
+
+
+ + +
+
+
+
+ ))} +
+
+ ); +}; + +export default PostMobile; diff --git a/src/components/post/post.tsx b/src/components/post/post.tsx index da4d884d..fd1303ac 100644 --- a/src/components/post/post.tsx +++ b/src/components/post/post.tsx @@ -1,24 +1,13 @@ -import { useEffect, useRef, useState } from 'react'; -import { Link, useLocation, useParams } from 'react-router-dom'; -import { Trans, useTranslation } from 'react-i18next'; -import { Role, useAccount, useSubplebbit } from '@plebbit/plebbit-react-hooks'; -import Plebbit from '@plebbit/plebbit-js/dist/browser/index.js'; -import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils'; -import { getFormattedDate } from '../../lib/utils/time-utils'; +import { useLocation, useParams } from 'react-router-dom'; +import { Role, useSubplebbit } from '@plebbit/plebbit-react-hooks'; import { isPendingPostView, isPostPageView } from '../../lib/utils/view-utils'; -import useCountLinksInReplies from '../../hooks/use-count-links-in-replies'; -import useReplies from '../../hooks/use-replies'; -import useStateString from '../../hooks/use-state-string'; import useWindowWidth from '../../hooks/use-window-width'; import styles from './post.module.css'; -import CommentMedia from '../comment-media'; -import { canEmbed } from '../embed'; -import LoadingEllipsis from '../loading-ellipsis'; -import Markdown from '../markdown'; +import PostDesktop from './post-desktop'; +import PostMobile from './post-mobile'; import _ from 'lodash'; -import { getDisplayMediaInfoType } from '../../lib/utils/media-utils'; -interface PostProps { +export interface PostProps { index?: number; isInPostPage?: boolean; isPendingPostPage?: boolean; @@ -29,447 +18,6 @@ interface PostProps { openReplyModal?: (cid: string) => void; } -const PostInfoDesktop = ({ isInPostPage, openReplyModal, post, roles }: PostProps) => { - const { t } = useTranslation(); - const { author, cid, locked, pinned, parentCid, postCid, shortCid, state, subplebbitAddress, timestamp, title } = post || {}; - const { address, displayName, shortAddress } = author || {}; - const { isDescription, isRules } = post || {}; // custom properties, not from api - - const stateString = useStateString(post); - - const isReply = parentCid; - - const shortDisplayName = displayName?.trim().length > 20 ? displayName?.trim().slice(0, 20).trim() + '...' : displayName?.trim(); - const authorRole = roles?.[address]?.role; - const displayTitle = title && title.length > 75 ? title?.slice(0, 75) + '...' : title; - - // pending reply by account is not yet published - const account = useAccount(); - const accountShortAddress = account?.author?.shortAddress; - - const [menuBtnRotated, setMenuBtnRotated] = useState(false); - const menuRef = useRef(null); - - const handleMenuButtonClick = () => { - setMenuBtnRotated((prev) => !prev); - }; - - const handleClickOutside = (event: MouseEvent) => { - if (menuRef.current && !menuRef.current.contains(event.target as Node)) { - setMenuBtnRotated(false); - } - }; - - useEffect(() => { - if (menuBtnRotated) { - document.addEventListener('mousedown', handleClickOutside); - } else { - document.removeEventListener('mousedown', handleClickOutside); - } - return () => { - document.removeEventListener('mousedown', handleClickOutside); - }; - }, [menuBtnRotated]); - - return ( -
- - - - {title && {displayTitle} } - - - {shortDisplayName || _.capitalize(t('anonymous'))} - {authorRole && ` ## Board ${authorRole}`}{' '} - - {!(isDescription || isRules) && (u/{shortAddress || accountShortAddress}) } - - - {getFormattedDate(timestamp)} - {isDescription || isRules ? '' : ' '} - - - {!(isDescription || isRules) && ( - - !cid && e.preventDefault()}> - c/ - - {!cid ? ( - {state === 'failed' || stateString === 'Failed' ? 'Failed' : 'Pending'} - ) : ( - openReplyModal && openReplyModal(cid)}> - {shortCid} - - )} - - )} - {pinned && ( - - - - )} - {locked && ( - - - - )} - {!isInPostPage && !isReply && ( - - [{t('reply')}] - - )} - - - - ▶ - - -
- ); -}; - -const PostMediaDesktop = ({ post }: PostProps) => { - const { t } = useTranslation(); - const { link, linkHeight, linkWidth, parentCid } = post || {}; - const { isDescription, isRules } = post || {}; // custom properties, not from api - const commentMediaInfo = getCommentMediaInfo(post); - const { type, url } = commentMediaInfo || {}; - const embedUrl = url && new URL(url); - const hasThumbnail = getHasThumbnail(commentMediaInfo, link); - const [showThumbnail, setShowThumbnail] = useState(true); - - const isReply = parentCid; - - return ( - url && ( -
-
- {t('link')}:{' '} - - {url.length > 30 ? url.slice(0, 30) + '...' : url} - {' '} - ({type && _.lowerCase(getDisplayMediaInfoType(type, t))}) - {!showThumbnail && (type === 'iframe' || type === 'video' || type === 'audio') && ( - - {' '} - [ - setShowThumbnail(true)}> - {t('close')} - - ] - - )} - {showThumbnail && !hasThumbnail && embedUrl && canEmbed(embedUrl) && ( - - {' '} - [ - setShowThumbnail(false)}> - {t('open')} - - ] - - )} -
- {(hasThumbnail || (!hasThumbnail && !showThumbnail)) && ( - - )} -
- ) - ); -}; - -const PostMessageDesktop = ({ isInPostPage, post }: PostProps) => { - const { cid, content, parentCid, postCid, state, subplebbitAddress } = post || {}; - const displayContent = content && !isInPostPage && content.length > 1000 ? content?.slice(0, 1000) + '(...)' : content; - - const isReply = parentCid; - const isReplyingToReply = postCid !== parentCid; - - const stateString = useStateString(post); - - const loadingString = stateString && ( -
{stateString !== 'Failed' ? : stateString}
- ); - - return ( - content && ( -
- {isReply && isReplyingToReply && ( - <> - - {`c/${parentCid && Plebbit.getShortCid(parentCid)}`} - -
- - )} - - {!isReply && content.length > 1000 && !isInPostPage && ( - -
- }} /> -
- )} - {!cid && state === 'pending' && stateString !== 'Failed' && ( - <> -
- {loadingString} - - )} -
- ) - ); -}; - -const PostDesktop = ({ isInPostPage, isPendingPostPage, openReplyModal, post, roles, showAllReplies }: PostProps) => { - const { cid, content, pinned, replyCount, subplebbitAddress } = post || {}; - const { isDescription, isRules } = post || {}; // custom properties, not from api - - const replies = useReplies(post); - const visiblelinksCount = useCountLinksInReplies(post, 5); - const totallinksCount = useCountLinksInReplies(post); - const repliesCount = pinned ? replyCount : replyCount - 5; - const linksCount = pinned ? totallinksCount : totallinksCount - visiblelinksCount; - - return ( -
-
-
-
- {!isInPostPage && ( - - - - )} - - - {!content &&
} - - {!isDescription && !isRules && !isPendingPostPage && (replies.length > 5 || (pinned && replies.length > 0)) && !isInPostPage && ( - - - - - {linksCount > 0 ? ( - }} - values={{ repliesCount, linksCount }} - /> - ) : ( - }} values={{ repliesCount }} /> - )} - - )} - {!(pinned && !isInPostPage) && - !isPendingPostPage && - !isDescription && - !isRules && - replies && - (showAllReplies ? replies : replies.slice(-5)).map((reply, index) => ( -
-
-
{'>>'}
-
- - - -
-
-
- ))} -
- ); -}; - -const PostInfoAndMediaMobile = ({ openReplyModal, post, roles }: PostProps) => { - const { t } = useTranslation(); - const { author, cid, link, linkHeight, linkWidth, locked, parentCid, pinned, shortCid, state, subplebbitAddress, timestamp, title } = post || {}; - const { isDescription, isRules } = post || {}; // custom properties, not from api - const { address, displayName, shortAddress } = author || {}; - - const authorRole = roles?.[address]?.role; - const shortDisplayName = displayName?.trim().length > 20 ? displayName?.trim().slice(0, 20).trim() + '...' : displayName?.trim(); - const displayTitle = title && title.length > 30 ? title?.slice(0, 30) + '(...)' : title; - - const commentMediaInfo = getCommentMediaInfo(post); - const hasThumbnail = getHasThumbnail(commentMediaInfo, link); - const [showThumbnail, setShowThumbnail] = useState(true); - - const isReply = parentCid; - - // pending reply by account is not yet published - const account = useAccount(); - const accountShortAddress = account?.author?.shortAddress; - - const stateString = useStateString(post); - - return ( - <> -
- - ... - - - - {shortDisplayName || _.capitalize(t('anonymous'))} - {authorRole && ` ## Board ${authorRole}`}{' '} - - {!(isDescription || isRules) && (u/{shortAddress || accountShortAddress})} - {pinned && ( - - - - )} - {locked && ( - - - - )} - {title && ( - <> -
- {displayTitle} - - )} -
- - {getFormattedDate(timestamp)}{' '} - {!(isDescription || isRules) && ( - - !cid && e.preventDefault()}> - c/ - - {!cid ? ( - {state === 'failed' || stateString === 'Failed' ? 'Failed' : 'Pending'} - ) : ( - openReplyModal && openReplyModal(cid)}> - {shortCid} - - )} - - )} - -
- {(hasThumbnail || link) && ( - - )} - - ); -}; - -const PostMessageMobile = ({ isInPostPage, post }: PostProps) => { - const { cid, content, parentCid, postCid, state, subplebbitAddress } = post || {}; - const displayContent = content && !isInPostPage && content.length > 1000 ? content?.slice(0, 1000) : content; - - const isReply = parentCid; - const isReplyingToReply = postCid !== parentCid; - - const stateString = useStateString(post); - - const loadingString = stateString && ( -
{stateString !== 'Failed' ? : stateString}
- ); - - return ( - content && ( -
- {isReply && isReplyingToReply && ( - <> - - {`c/${parentCid && Plebbit.getShortCid(parentCid)}`} - -
- - )} - - {!isReply && content.length > 1000 && !isInPostPage && ( - -
- }} /> -
- )} - {!cid && state === 'pending' && stateString !== 'Failed' && ( - <> -
- {loadingString} - - )} -
- ) - ); -}; - -const PostMobile = ({ isInPostPage, isPendingPostPage, openReplyModal, post, roles, showAllReplies }: PostProps) => { - const { t } = useTranslation(); - const { cid, pinned, replyCount, subplebbitAddress } = post || {}; - const { isDescription, isRules } = post || {}; // custom properties, not from api - - const linksCount = useCountLinksInReplies(post); - - const replies = useReplies(post); - - return ( -
-
-
-
-
-
-
- - -
- {!isInPostPage && !isPendingPostPage && ( -
- - {replyCount > 0 && `${replyCount} Replies`} - {linksCount > 0 && ` / ${linksCount} Links`} - - - {t('view_thread')} - -
- )} -
- {!(pinned && !isInPostPage) && - !isPendingPostPage && - !isDescription && - !isRules && - replies && - (showAllReplies ? replies : replies.slice(-5)).map((reply) => ( -
-
-
-
- - -
-
-
-
- ))} -
-
- ); -}; - const Post = ({ post, showAllReplies = false, openReplyModal }: PostProps) => { const subplebbit = useSubplebbit({ subplebbitAddress: post?.subplebbitAddress }); const isMobile = useWindowWidth() < 640;