diff --git a/src/components/board-header/board-header.tsx b/src/components/board-header/board-header.tsx index dbcc7bd5..cf7f0ae9 100644 --- a/src/components/board-header/board-header.tsx +++ b/src/components/board-header/board-header.tsx @@ -45,7 +45,9 @@ const BoardHeader = () => { )}
{title || `p/${shortAddress || subplebbitAddress}`} - {(isOffline || isOnlineStatusLoading) && } + {(isOffline || isOnlineStatusLoading) && !isInAllView && !isInSubscriptionsView && ( + + )}
{subtitle}

diff --git a/src/components/markdown/markdown.module.css b/src/components/markdown/markdown.module.css index fe6b2eec..9ecbb5e8 100644 --- a/src/components/markdown/markdown.module.css +++ b/src/components/markdown/markdown.module.css @@ -8,10 +8,6 @@ text-decoration: var(--post-content-link-text-decoration-hover); } -.markdown li { - list-style-position: inside; -} - .hrWrapper { padding: 0.5em 0; } diff --git a/src/components/post-desktop/post-desktop.tsx b/src/components/post-desktop/post-desktop.tsx index 768afb7b..1a149f92 100644 --- a/src/components/post-desktop/post-desktop.tsx +++ b/src/components/post-desktop/post-desktop.tsx @@ -102,9 +102,9 @@ const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }: {!(isDescription || isRules) && ( <> {isReply && author?.avatar && ( - <> - {' '} - + + + )} (u/ { const stateString = useStateString(post); - const loadingString = stateString && ( -
{stateString !== 'Failed' ? : stateString}
+ const loadingString = ( +
{stateString !== 'Failed' ? : stateString}
); return ( @@ -341,7 +341,7 @@ const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => { post = editedComment; } - const { author, cid, content, link, postCid, subplebbitAddress } = post || {}; + const { author, cid, link, postCid, subplebbitAddress } = post || {}; const isRouteLinkToReply = useLocation().pathname.startsWith(`/p/${subplebbitAddress}/c/${cid}`); const { hidden } = useHide({ cid }); @@ -356,7 +356,7 @@ const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => { > {link && !hidden && isValidURL(link) && } - {content && !hidden && } + {!hidden && } ); @@ -434,7 +434,7 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies )}
)} - {post?.replyCount === undefined && ( + {post?.replyCount === undefined && !isInPendingPostView && ( @@ -452,14 +452,15 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies ))} - {stateString && stateString !== 'Failed' ? ( -
-
- -
- ) : ( - state === 'failed' && {t('failed')} - )} + {!isInPendingPostView && + (stateString && stateString !== 'Failed' ? ( +
+
+ +
+ ) : ( + state === 'failed' && {t('failed')} + ))} ); }; diff --git a/src/components/post-mobile/post-mobile.tsx b/src/components/post-mobile/post-mobile.tsx index 980e7ec3..0eb84770 100644 --- a/src/components/post-mobile/post-mobile.tsx +++ b/src/components/post-mobile/post-mobile.tsx @@ -75,9 +75,9 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P {!(isDescription || isRules) && ( <> {isReply && author?.avatar && ( - <> - {' '} - + + + )} (u/ { const stateString = useStateString(post); - const loadingString = stateString && ( -
{stateString !== 'Failed' ? : stateString}
+ const loadingString = ( +
{stateString !== 'Failed' ? : stateString}
); return ( - content && ( -
- {isReply && !(removed || deleted) && state !== 'failed' && isReplyingToReply && } - {removed ? ( +
+ {isReply && !(removed || deleted) && state !== 'failed' && isReplyingToReply && } + {removed ? ( + ({t('this_post_was_removed')})} + content={`${_.capitalize(t('reason'))}: "${reason}"`} + showTooltip={!!reason} + /> + ) : deleted ? ( + {t('user_deleted_this_post')}} content={reason && `${t('reason')}: ${reason}`} /> + ) : ( + <> + {!showOriginal && } + {edit && original?.content !== post?.content && ( + + {showOriginal && } +
+ {t('comment_edited_at_timestamp', { timestamp: getFormattedDate(edit?.timestamp), interpolation: { escapeValue: false } })}{' '} + {reason && <>{t('reason_reason', { reason: reason, interpolation: { escapeValue: false } })} } + {showOriginal ? ( + setShowOriginal(!showOriginal)} /> }} + /> + ) : ( + setShowOriginal(!showOriginal)} /> }} + /> + )} +
+ )} + + )} + {/* TODO: commentAuthor is not available outside of editedComment, update when available */} + {/* {banned && ( + +
+
({t('this_post_was_removed')})
} - content={`${_.capitalize(t('reason'))}: "${reason}"`} - showTooltip={!!reason} + children={`(${t('user_banned')})`} + content={`${t('ban_expires_at', { + address: subplebbitAddress && Plebbit.getShortAddress(subplebbitAddress), + timestamp: getFormattedDate(commentAuthor?.banExpiresAt), + interpolation: { escapeValue: false }, + })}${reason ? `. ${_.capitalize(t('reason'))}: "${reason}"` : ''}`} /> - ) : deleted ? ( - {t('user_deleted_this_post')}} content={reason && `${t('reason')}: ${reason}`} /> - ) : ( - <> - {!showOriginal && } - {edit && original?.content !== post?.content && ( - - {showOriginal && } -
- {t('comment_edited_at_timestamp', { timestamp: getFormattedDate(edit?.timestamp), interpolation: { escapeValue: false } })}{' '} - {reason && <>{t('reason_reason', { reason: reason, interpolation: { escapeValue: false } })} } - {showOriginal ? ( - setShowOriginal(!showOriginal)} /> }} - /> - ) : ( - setShowOriginal(!showOriginal)} /> }} - /> - )} -
- )} - - )} - {/* TODO: commentAuthor is not available outside of editedComment, update when available */} - {/* {banned && ( - -
-
- -
- )} */} - {!isReply && content.length > 1000 && !isInPostView && ( - -
- }} /> -
- )} - {!cid && state === 'pending' && stateString !== 'Failed' && ( - <> -
- {loadingString} - - )} -
- ) + + )} */} + {!isReply && content.length > 1000 && !isInPostView && ( + +
+ }} /> +
+ )} + {!cid && state === 'pending' && stateString !== 'Failed' && ( + <> +
+ {loadingString} + + )} +
); }; @@ -261,7 +259,7 @@ const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => { if (editedComment) { post = editedComment; } - const { author, cid, content, postCid, subplebbitAddress } = post || {}; + const { author, cid, postCid, subplebbitAddress } = post || {}; const isRouteLinkToReply = useLocation().pathname.startsWith(`/p/${subplebbitAddress}/c/${cid}`); const { hidden } = useHide({ cid }); @@ -275,7 +273,7 @@ const Reply = ({ openReplyModal, postReplyCount, reply, roles }: PostProps) => { data-post-cid={postCid} > - {content && !hidden && } + {!hidden && } @@ -348,7 +346,7 @@ const PostMobile = ({ openReplyModal, post, roles, showAllReplies, showReplies = )} - {post?.replyCount === undefined && ( + {post?.replyCount === undefined && !isInPendingPostView && ( @@ -365,13 +363,14 @@ const PostMobile = ({ openReplyModal, post, roles, showAllReplies, showReplies = ))} - {stateString && stateString !== 'Failed' ? ( -
- -
- ) : ( - state === 'failed' && {t('failed')} - )} + {!isInPendingPostView && + (stateString && stateString !== 'Failed' ? ( +
+ +
+ ) : ( + state === 'failed' && {t('failed')} + ))} )} diff --git a/src/components/reply-modal/reply-modal.tsx b/src/components/reply-modal/reply-modal.tsx index b4a1a440..c8842b93 100644 --- a/src/components/reply-modal/reply-modal.tsx +++ b/src/components/reply-modal/reply-modal.tsx @@ -86,11 +86,16 @@ const ReplyModal = ({ closeModal, parentCid, scrollY }: ReplyModalProps) => { if (ref) { textRef.current = ref; !isMobile && ref.focus(); - const len = ref.value.length; - ref.setSelectionRange(len, len); } }; + useEffect(() => { + if (textRef.current) { + const len = textRef.current.value.length; + textRef.current.setSelectionRange(len, len); + } + }, []); + const contentPrefix = `c/${parentCid && Plebbit.getShortCid(parentCid)}\n`; const handleContentInput = (e: React.ChangeEvent) => { diff --git a/src/components/settings-modal/avatar-settings/avatar-settings.module.css b/src/components/settings-modal/avatar-settings/avatar-settings.module.css index 163afba8..409947bb 100644 --- a/src/components/settings-modal/avatar-settings/avatar-settings.module.css +++ b/src/components/settings-modal/avatar-settings/avatar-settings.module.css @@ -4,7 +4,6 @@ border: 1px solid #aaa; background-color: var(--avatar-background-color, white); margin-left: 10px; - border-radius: 32px; overflow: hidden; } diff --git a/src/hooks/use-is-subplebbit-offline.ts b/src/hooks/use-is-subplebbit-offline.ts index 70181ff8..f94c2f64 100644 --- a/src/hooks/use-is-subplebbit-offline.ts +++ b/src/hooks/use-is-subplebbit-offline.ts @@ -3,11 +3,13 @@ import { useEffect } from 'react'; import { Subplebbit } from '@plebbit/plebbit-react-hooks'; import { getFormattedTimeAgo } from '../lib/utils/time-utils'; import useSubplebbitOfflineStore from '../stores/use-subplebbit-offline-store'; +import useSubplebbitsLoadingStartTimestamps from './use-subplebbits-loading-start-timestamps-store'; const useIsSubplebbitOffline = (subplebbit: Subplebbit) => { const { t } = useTranslation(); const { address, state, updatedAt, updatingState } = subplebbit || {}; const { subplebbitOfflineState, setSubplebbitOfflineState, initializesubplebbitOfflineState } = useSubplebbitOfflineStore(); + const subplebbitsLoadingStartTimestamps = useSubplebbitsLoadingStartTimestamps([address]); useEffect(() => { if (address && !subplebbitOfflineState[address]) { @@ -22,15 +24,13 @@ const useIsSubplebbitOffline = (subplebbit: Subplebbit) => { }, [address, state, updatedAt, updatingState, setSubplebbitOfflineState]); const subplebbitOfflineStore = subplebbitOfflineState[address] || { initialLoad: true }; + const loadingStartTimestamp = subplebbitsLoadingStartTimestamps[0] || 0; - const isLoading = - updatingState === 'resolving-address' || - (updatingState === 'stopped' && state === 'stopped' && !updatedAt) || - (subplebbitOfflineStore.initialLoad && updatingState === 'fetching-ipns' && !updatedAt); + const isLoading = subplebbitOfflineStore.initialLoad && (!updatedAt || Date.now() / 1000 - updatedAt >= 60 * 60) && Date.now() / 1000 - loadingStartTimestamp < 30; - const isOffline = - !isLoading && ((updatedAt && updatedAt < Date.now() / 1000 - 60 * 60) || updatingState === 'failed' || (updatingState === 'fetching-ipns' && !updatedAt)); + const isOffline = !isLoading && ((updatedAt && updatedAt < Date.now() / 1000 - 60 * 60) || (!updatedAt && Date.now() / 1000 - loadingStartTimestamp >= 30)); + const isOnline = updatedAt && Date.now() / 1000 - updatedAt < 60 * 60; const offlineIconClass = isLoading ? 'yellowOfflineIcon' : isOffline ? 'redOfflineIcon' : ''; const offlineTitle = isLoading @@ -39,7 +39,7 @@ const useIsSubplebbitOffline = (subplebbit: Subplebbit) => { ? isOffline && t('posts_last_synced_info', { time: getFormattedTimeAgo(updatedAt), interpolation: { escapeValue: false } }) : t('subplebbit_offline_info'); - return { isOffline, isOnlineStatusLoading: isLoading, offlineIconClass, offlineTitle }; + return { isOffline: !isOnline && isOffline, isOnlineStatusLoading: !isOnline && isLoading, offlineIconClass, offlineTitle }; }; export default useIsSubplebbitOffline; diff --git a/src/hooks/use-subplebbits-loading-start-timestamps-store.ts b/src/hooks/use-subplebbits-loading-start-timestamps-store.ts new file mode 100644 index 00000000..80ecdb5b --- /dev/null +++ b/src/hooks/use-subplebbits-loading-start-timestamps-store.ts @@ -0,0 +1,42 @@ +import { useEffect, useMemo } from 'react'; +import { create } from 'zustand'; + +interface SubplebbitsLoadingStartTimestampsState { + timestamps: Record; + addSubplebbits: (subplebbitAddresses: string[]) => void; +} + +const useSubplebbitsLoadingStartTimestampsStore = create((set, get) => ({ + timestamps: {}, + addSubplebbits: (subplebbitAddresses) => { + const { timestamps } = get(); + const newTimestamps: Record = {}; + subplebbitAddresses.forEach((subplebbitAddress) => { + if (!timestamps[subplebbitAddress]) { + newTimestamps[subplebbitAddress] = Math.round(Date.now() / 1000); + } + }); + if (Object.keys(newTimestamps).length) { + set((state) => ({ timestamps: { ...state.timestamps, ...newTimestamps } })); + } + }, +})); + +const useSubplebbitsLoadingStartTimestamps = (subplebbitAddresses?: string[]) => { + const timestampsStore = useSubplebbitsLoadingStartTimestampsStore((state) => state.timestamps); + const addSubplebbits = useSubplebbitsLoadingStartTimestampsStore((state) => state.addSubplebbits); + + useEffect(() => { + if (subplebbitAddresses) { + addSubplebbits(subplebbitAddresses); + } + }, [subplebbitAddresses, addSubplebbits]); + + const subplebbitsLoadingStartTimestamps = useMemo(() => { + return subplebbitAddresses?.map((subplebbitAddress) => timestampsStore[subplebbitAddress]) || []; + }, [timestampsStore, subplebbitAddresses]); + + return subplebbitsLoadingStartTimestamps; +}; + +export default useSubplebbitsLoadingStartTimestamps; diff --git a/src/views/post/post.module.css b/src/views/post/post.module.css index 2583f8a3..5c9e6bb0 100644 --- a/src/views/post/post.module.css +++ b/src/views/post/post.module.css @@ -505,22 +505,23 @@ } .authorAvatar { - width: 22px; - height: 22px; - background-size: 100%; - background-position: center; - background-repeat: no-repeat; + width: 24px; display: inline-block; - vertical-align: top; + position: relative; + padding-right: 2px; } +.authorAvatar img { + width: 24px; + max-height: 24px; + position: absolute; + bottom: -5px; +} .stateString { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; - width: calc(100vw - 28px); - width: 100vw; } @media (max-width: 640px) { @@ -542,11 +543,13 @@ } .authorAvatar { - vertical-align: middle; + width: 20px; } - .stateString { - padding-left: 5px; + .authorAvatar img { + width: 20px; + max-height: 20px; + bottom: -3px; } } @@ -568,8 +571,4 @@ .replyMobile { display: none; } - - .stateString { - padding-top: 10px; - } } \ No newline at end of file