Merge pull request #459 from plebbit/development

Development
This commit is contained in:
Tom (plebeius.eth)
2024-07-23 12:23:58 +02:00
committed by GitHub
9 changed files with 168 additions and 125 deletions
+3 -1
View File
@@ -45,7 +45,9 @@ const BoardHeader = () => {
)}
<div className={styles.boardTitle}>
{title || `p/${shortAddress || subplebbitAddress}`}
{(isOffline || isOnlineStatusLoading) && <span className={`${styles.offlineIcon} ${offlineIconClass}`} title={offlineTitle} />}
{(isOffline || isOnlineStatusLoading) && !isInAllView && !isInSubscriptionsView && (
<span className={`${styles.offlineIcon} ${offlineIconClass}`} title={offlineTitle} />
)}
</div>
<div className={styles.boardSubtitle}>{subtitle}</div>
<hr />
@@ -8,10 +8,6 @@
text-decoration: var(--post-content-link-text-decoration-hover);
}
.markdown li {
list-style-position: inside;
}
.hrWrapper {
padding: 0.5em 0;
}
+17 -16
View File
@@ -102,9 +102,9 @@ const PostInfo = ({ openReplyModal, post, postReplyCount = 0, roles, isHidden }:
{!(isDescription || isRules) && (
<>
{isReply && author?.avatar && (
<>
<span className={styles.authorAvatar} style={{ backgroundImage: `url(${avatarImageUrl})` }} />{' '}
</>
<span className={styles.authorAvatar}>
<img src={avatarImageUrl} alt='' />
</span>
)}
(u/
<Tooltip
@@ -256,8 +256,8 @@ const PostMessage = ({ post }: PostProps) => {
const stateString = useStateString(post);
const loadingString = stateString && (
<div className={`${styles.stateString} ${styles.ellipsis}`}>{stateString !== 'Failed' ? <LoadingEllipsis string={stateString} /> : stateString}</div>
const loadingString = (
<div className={`${styles.stateString} ${styles.ellipsis}`}>{stateString !== 'Failed' ? <LoadingEllipsis string={stateString || t('loading')} /> : stateString}</div>
);
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) => {
>
<PostInfo openReplyModal={openReplyModal} post={post} postReplyCount={postReplyCount} roles={roles} />
{link && !hidden && isValidURL(link) && <PostMedia post={post} />}
{content && !hidden && <PostMessage post={post} />}
{!hidden && <PostMessage post={post} />}
</div>
</div>
);
@@ -434,7 +434,7 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
)}
</span>
)}
{post?.replyCount === undefined && (
{post?.replyCount === undefined && !isInPendingPostView && (
<span className={styles.loadingString}>
<LoadingEllipsis string={t('loading_comments')} />
</span>
@@ -452,14 +452,15 @@ const PostDesktop = ({ openReplyModal, post, roles, showAllReplies, showReplies
</div>
))}
</div>
{stateString && stateString !== 'Failed' ? (
<div className={styles.stateString}>
<br />
<LoadingEllipsis string={stateString} />
</div>
) : (
state === 'failed' && <span className={styles.error}>{t('failed')}</span>
)}
{!isInPendingPostView &&
(stateString && stateString !== 'Failed' ? (
<div className={styles.stateString}>
<br />
<LoadingEllipsis string={stateString} />
</div>
) : (
state === 'failed' && <span className={styles.error}>{t('failed')}</span>
))}
</div>
);
};
+78 -79
View File
@@ -75,9 +75,9 @@ const PostInfoAndMedia = ({ openReplyModal, post, postReplyCount = 0, roles }: P
{!(isDescription || isRules) && (
<>
{isReply && author?.avatar && (
<>
<span className={styles.authorAvatar} style={{ backgroundImage: `url(${avatarImageUrl})` }} />{' '}
</>
<span className={styles.authorAvatar}>
<img src={avatarImageUrl} alt='' />
</span>
)}
(u/
<Tooltip
@@ -180,77 +180,75 @@ const PostMessageMobile = ({ post }: PostProps) => {
const stateString = useStateString(post);
const loadingString = stateString && (
<div className={`${styles.stateString} ${styles.ellipsis}`}>{stateString !== 'Failed' ? <LoadingEllipsis string={stateString} /> : stateString}</div>
const loadingString = (
<div className={`${styles.stateString} ${styles.ellipsis}`}>{stateString !== 'Failed' ? <LoadingEllipsis string={stateString || t('loading')} /> : stateString}</div>
);
return (
content && (
<blockquote className={`${styles.postMessage} ${!isReply && styles.clampLines}`}>
{isReply && !(removed || deleted) && state !== 'failed' && isReplyingToReply && <ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={quotelinkReply} />}
{removed ? (
<blockquote className={`${styles.postMessage} ${!isReply && styles.clampLines}`}>
{isReply && !(removed || deleted) && state !== 'failed' && isReplyingToReply && <ReplyQuotePreview isQuotelinkReply={true} quotelinkReply={quotelinkReply} />}
{removed ? (
<Tooltip
children={<span className={styles.removedContent}>({t('this_post_was_removed')})</span>}
content={`${_.capitalize(t('reason'))}: "${reason}"`}
showTooltip={!!reason}
/>
) : deleted ? (
<Tooltip children={<span className={styles.deletedContent}>{t('user_deleted_this_post')}</span>} content={reason && `${t('reason')}: ${reason}`} />
) : (
<>
{!showOriginal && <Markdown content={displayContent} />}
{edit && original?.content !== post?.content && (
<span className={styles.editedInfo}>
{showOriginal && <Markdown content={original?.content} />}
<br />
{t('comment_edited_at_timestamp', { timestamp: getFormattedDate(edit?.timestamp), interpolation: { escapeValue: false } })}{' '}
{reason && <>{t('reason_reason', { reason: reason, interpolation: { escapeValue: false } })} </>}
{showOriginal ? (
<Trans
i18nKey={'click_here_to_hide_original'}
shouldUnescape={true}
components={{ 1: <span className={styles.showOriginal} onClick={() => setShowOriginal(!showOriginal)} /> }}
/>
) : (
<Trans
i18nKey={'click_here_to_show_original'}
shouldUnescape={true}
components={{ 1: <span className={styles.showOriginal} onClick={() => setShowOriginal(!showOriginal)} /> }}
/>
)}
</span>
)}
</>
)}
{/* TODO: commentAuthor is not available outside of editedComment, update when available */}
{/* {banned && (
<span className={styles.removedContent}>
<br />
<br />
<Tooltip
children={<span className={styles.removedContent}>({t('this_post_was_removed')})</span>}
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 ? (
<Tooltip children={<span className={styles.deletedContent}>{t('user_deleted_this_post')}</span>} content={reason && `${t('reason')}: ${reason}`} />
) : (
<>
{!showOriginal && <Markdown content={displayContent} />}
{edit && original?.content !== post?.content && (
<span className={styles.editedInfo}>
{showOriginal && <Markdown content={original?.content} />}
<br />
{t('comment_edited_at_timestamp', { timestamp: getFormattedDate(edit?.timestamp), interpolation: { escapeValue: false } })}{' '}
{reason && <>{t('reason_reason', { reason: reason, interpolation: { escapeValue: false } })} </>}
{showOriginal ? (
<Trans
i18nKey={'click_here_to_hide_original'}
shouldUnescape={true}
components={{ 1: <span className={styles.showOriginal} onClick={() => setShowOriginal(!showOriginal)} /> }}
/>
) : (
<Trans
i18nKey={'click_here_to_show_original'}
shouldUnescape={true}
components={{ 1: <span className={styles.showOriginal} onClick={() => setShowOriginal(!showOriginal)} /> }}
/>
)}
</span>
)}
</>
)}
{/* TODO: commentAuthor is not available outside of editedComment, update when available */}
{/* {banned && (
<span className={styles.removedContent}>
<br />
<br />
<Tooltip
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}"` : ''}`}
/>
</span>
)} */}
{!isReply && content.length > 1000 && !isInPostView && (
<span className={styles.abbr}>
<br />
<Trans i18nKey={'comment_too_long'} shouldUnescape={true} components={{ 1: <Link to={`/p/${subplebbitAddress}/c/${cid}`} /> }} />
</span>
)}
{!cid && state === 'pending' && stateString !== 'Failed' && (
<>
<br />
{loadingString}
</>
)}
</blockquote>
)
</span>
)} */}
{!isReply && content.length > 1000 && !isInPostView && (
<span className={styles.abbr}>
<br />
<Trans i18nKey={'comment_too_long'} shouldUnescape={true} components={{ 1: <Link to={`/p/${subplebbitAddress}/c/${cid}`} /> }} />
</span>
)}
{!cid && state === 'pending' && stateString !== 'Failed' && (
<>
<br />
{loadingString}
</>
)}
</blockquote>
);
};
@@ -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}
>
<PostInfoAndMedia openReplyModal={openReplyModal} post={post} postReplyCount={postReplyCount} roles={roles} />
{content && !hidden && <PostMessageMobile post={post} />}
{!hidden && <PostMessageMobile post={post} />}
<ReplyBacklinks post={reply} />
</div>
</div>
@@ -348,7 +346,7 @@ const PostMobile = ({ openReplyModal, post, roles, showAllReplies, showReplies =
</div>
)}
</div>
{post?.replyCount === undefined && (
{post?.replyCount === undefined && !isInPendingPostView && (
<span className={styles.loadingString}>
<LoadingEllipsis string={t('loading_comments')} />
</span>
@@ -365,13 +363,14 @@ const PostMobile = ({ openReplyModal, post, roles, showAllReplies, showReplies =
</div>
))}
</div>
{stateString && stateString !== 'Failed' ? (
<div className={styles.stateString}>
<LoadingEllipsis string={stateString} />
</div>
) : (
state === 'failed' && <span className={styles.error}>{t('failed')}</span>
)}
{!isInPendingPostView &&
(stateString && stateString !== 'Failed' ? (
<div className={styles.stateString}>
<LoadingEllipsis string={stateString} />
</div>
) : (
state === 'failed' && <span className={styles.error}>{t('failed')}</span>
))}
</div>
)}
</>
+7 -2
View File
@@ -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<HTMLTextAreaElement>) => {
@@ -4,7 +4,6 @@
border: 1px solid #aaa;
background-color: var(--avatar-background-color, white);
margin-left: 10px;
border-radius: 32px;
overflow: hidden;
}
+7 -7
View File
@@ -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;
@@ -0,0 +1,42 @@
import { useEffect, useMemo } from 'react';
import { create } from 'zustand';
interface SubplebbitsLoadingStartTimestampsState {
timestamps: Record<string, number>;
addSubplebbits: (subplebbitAddresses: string[]) => void;
}
const useSubplebbitsLoadingStartTimestampsStore = create<SubplebbitsLoadingStartTimestampsState>((set, get) => ({
timestamps: {},
addSubplebbits: (subplebbitAddresses) => {
const { timestamps } = get();
const newTimestamps: Record<string, number> = {};
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;
+14 -15
View File
@@ -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;
}
}